#!/usr/bin/env bash # Scribe plugin — SessionStart push channel. # # Curls the operator's Scribe instance for always-on rules + active-project # context and emits it as SessionStart `additionalContext`. Config comes from # the plugin's userConfig, which Claude Code exports to hook subprocesses as # CLAUDE_PLUGIN_OPTION_ env vars: # CLAUDE_PLUGIN_OPTION_api_endpoint base URL, no trailing slash # CLAUDE_PLUGIN_OPTION_api_token fmcp_ API key (sensitive) # CLAUDE_PLUGIN_OPTION_project_id optional numeric project id # # IMPORTANT: do NOT pass these via `${user_config.*}` substitution in # hooks.json — sensitive userConfig values (api_token) are kept in the keychain # and are never spliced into a hook command line, so the placeholder arrives # unexpanded. The harness env vars above are the supported channel. SCRIBE_URL # / SCRIBE_TOKEN / SCRIBE_PROJECT_ID still override, for the settings.json # dogfooding path where the hook is wired up by hand. # # FAIL-OPEN: any missing tool/config, network error, or unreachable instance # injects nothing and exits 0. A session must never be blocked by Scribe. set -uo pipefail command -v jq >/dev/null 2>&1 || exit 0 command -v curl >/dev/null 2>&1 || exit 0 url=${SCRIBE_URL:-${CLAUDE_PLUGIN_OPTION_api_endpoint:-}} token=${SCRIBE_TOKEN:-${CLAUDE_PLUGIN_OPTION_api_token:-}} project_id=${SCRIBE_PROJECT_ID:-${CLAUDE_PLUGIN_OPTION_project_id:-}} # Guard against an unexpanded `${...}` placeholder reaching us as a literal — it # would otherwise be sent as a garbage Bearer token and 401. Treat as unset. case "$url" in *'${'*) url="" ;; esac case "$token" in *'${'*) token="" ;; esac case "$project_id" in *'${'*) project_id="" ;; esac [ -n "$url" ] && [ -n "$token" ] || exit 0 q="" [ -n "$project_id" ] && q="?project_id=${project_id}" body=$(curl -fsS --max-time 8 \ -H "Authorization: Bearer ${token}" \ "${url%/}/api/plugin/context${q}" 2>/dev/null) || exit 0 text=$(printf '%s' "$body" | jq -r '.context // empty' 2>/dev/null) || exit 0 [ -n "$text" ] || exit 0 jq -n --arg c "$text" \ '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $c}}' exit 0