Files
My-Brain-Is-Full-Crew/adapters/lib.sh
2026-04-10 17:10:27 +02:00

137 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# adapters/lib.sh — Shared helpers for framework adapters
# =============================================================================
# Sourced by scripts/build.sh BEFORE the framework-specific adapter.sh.
# Provides vocabulary tables, parsing helpers, filtering, and JSON utilities.
# Do NOT execute directly.
# =============================================================================
# ── Vocabulary constants ─────────────────────────────────────────────────────
# The closed set of capabilities the source frontmatter may declare.
# Adapters consult this list to validate frontmatter before translating.
CAPABILITY_VOCAB="read write edit bash webfetch websearch notebook task todo"
# The closed set of hook event names the source .hook.yaml may declare.
EVENT_VOCAB="before-tool-use after-tool-use on-notification on-session-start on-prompt-submit"
# ── Path rewriting ───────────────────────────────────────────────────────────
# rewrite_framework_paths <file> <target_fw_dir> <target_dispatcher>
# Rewrites source-canonical framework path references in a text file in-place.
# Source files use .claude/ and CLAUDE.md as the canonical form.
# Call this after copying any text file from source to dist for non-CC frameworks.
# No-op when target_fw_dir is "claude" (i.e. building for claude-code, whose
# canonical output paths already match the source form — no rewrite needed).
rewrite_framework_paths() {
local file="$1" tgt_dir="$2" tgt_dispatcher="$3"
[[ "$tgt_dir" == "claude" && "$tgt_dispatcher" == "CLAUDE.md" ]] && return 0
sed -i \
-e "s|\.claude/|.${tgt_dir}/|g" \
-e "s|CLAUDE\.md|${tgt_dispatcher}|g" \
"$file"
}
# ── Parsing helpers ──────────────────────────────────────────────────────────
# parse_frontmatter <file> <key>
# Echoes the value of a top-level YAML key from the file's --- ... --- block.
# Returns nothing (empty) if the key is not found.
# Supports scalar values and flat list values; preserves the raw value as-written.
parse_frontmatter() {
local file="$1" key="$2"
awk -v key="$key" '
/^---$/ { fm++; next }
fm == 1 {
# Match "key: value" — strip leading whitespace, capture value after first ":"
sub(/^[[:space:]]+/, "")
if (match($0, "^" key ":[[:space:]]*")) {
value = substr($0, RLENGTH + 1)
sub(/[[:space:]]+$/, "", value)
print value
exit
}
}
fm >= 2 { exit }
' "$file"
}
# parse_capabilities <agent_file>
# Echoes the agent's capabilities as space-separated tokens.
# Empty output if capabilities is missing or [].
parse_capabilities() {
local file="$1"
local raw; raw="$(parse_frontmatter "$file" capabilities)"
# Strip [ and ] and commas, leaving space-separated tokens
echo "$raw" | tr -d '[]' | tr ',' ' ' | xargs
}
# should_include <component_file> <framework>
# Exit 0 if the component should be included in the given framework's build.
# Exit 1 if the component's exclude: list contains the framework.
should_include() {
local file="$1" framework="$2"
local raw; raw="$(parse_frontmatter "$file" exclude)"
# Treat missing or empty exclude as "include"
[[ -z "$raw" || "$raw" == "[]" ]] && return 0
# Tokenize the list and check membership
local tokens; tokens="$(echo "$raw" | tr -d '[]' | tr ',' ' ' | xargs)"
for t in $tokens; do
[[ "$t" == "$framework" ]] && return 1
done
return 0
}
# parse_hook_yaml <hook_yaml_file>
# Emits key=value lines for each top-level scalar AND for trigger fields.
# Output keys: name, script, event, match-tool (space-separated tokens), exclude.
parse_hook_yaml() {
local file="$1"
awk '
/^name:/ { sub(/^name:[[:space:]]*/, ""); print "name=" $0 }
/^script:/ { sub(/^script:[[:space:]]*/, ""); print "script=" $0 }
/^[[:space:]]*-[[:space:]]*event:/ {
sub(/^[[:space:]]*-[[:space:]]*event:[[:space:]]*/, "")
print "event=" $0
}
/^[[:space:]]*match-tool:/ {
sub(/^[[:space:]]*match-tool:[[:space:]]*/, "")
gsub(/[\[\],]/, " ")
gsub(/^[[:space:]]+|[[:space:]]+$/, "")
gsub(/[[:space:]]+/, " ")
print "match-tool=" $0
}
/^exclude:/ { sub(/^exclude:[[:space:]]*/, ""); print "exclude=" $0 }
' "$file"
}
# agent_body <agent_file>
# Echoes everything after the closing --- of the frontmatter block.
agent_body() {
local file="$1"
awk '
fm < 2 && /^---$/ { fm++; next }
fm >= 2 { print }
' "$file"
}
# enumerate_agents <dir>
# Echoes one agent file path per line.
enumerate_agents() {
local dir="$1"
[[ -d "$dir" ]] || return 0
for f in "$dir"/*.md; do
[[ -f "$f" ]] && echo "$f"
done
}
# enumerate_hooks <dir>
# Echoes one .hook.yaml file path per line.
enumerate_hooks() {
local dir="$1"
[[ -d "$dir" ]] || return 0
for f in "$dir"/*.hook.yaml; do
[[ -f "$f" ]] && echo "$f"
done
}