CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 36s
Step 1 of the Scribe -> Grimoire rename: every capitalised "Scribe" in prose
becomes "Grimoire" ("Fabled Scribe" -> "Fabled Grimoire") across the UI,
the PWA manifest and page title, the MCP instructions and tool docstrings,
hook and skill text, docs and README, with the tests that assert on that
prose moving with it. Old migrations keep their wording.
Identifiers are untouched here: the package, plugin name, MCP server key,
hook file names, env vars, the .scribe marker and FabledScribe repo/image
names move in steps 2 and 3, along with prose that quotes them.
The sweep matched "Scribe" only as a whole word, so describe/subscribe and
friends are untouched. The longer name took the MCP instructions one
character over their 1,600 budget; two phrases are shortened to fit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
3.6 KiB
Bash
Executable File
86 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Grimoire plugin — sync stored Processes into auto-surfacing local skills.
|
|
#
|
|
# Fetches `GET /api/plugin/processes` and writes one
|
|
# ~/.claude/skills/scribe-proc-<slug>/SKILL.md
|
|
# per Process. The stub's frontmatter `description` is the auto-surface trigger;
|
|
# its body tells Claude to call get_process(<name>) and follow the LIVE procedure
|
|
# from Grimoire (the DB stays the single source of truth — the stub is a pointer).
|
|
#
|
|
# WHY LOCAL ~/.claude/skills (not the plugin dir): the plugin is git-cloned and
|
|
# identical on every install, so instance-specific stubs can't live inside it.
|
|
# Personal skills in ~/.claude/skills are live-detected by Claude Code within the
|
|
# session, so a freshly written stub auto-surfaces without a restart.
|
|
#
|
|
# TRIGGERS: this runs at SessionStart (alongside the context hook) so stubs stay
|
|
# fresh each session, and on demand via the `/scribe:sync` command.
|
|
#
|
|
# FAIL-OPEN & SILENT: never blocks a session; emits NOTHING on stdout (so it's
|
|
# safe as a second SessionStart hook). On any fetch failure it exits without
|
|
# touching existing stubs — a transient outage must not wipe the user's skills.
|
|
# Config mirrors the context hook: CLAUDE_PLUGIN_OPTION_API_ENDPOINT /
|
|
# CLAUDE_PLUGIN_OPTION_API_TOKEN (userConfig key UPPERCASED by Claude Code),
|
|
# with SCRIBE_URL / SCRIBE_TOKEN as the override.
|
|
set -uo pipefail
|
|
|
|
# shellcheck source=plugin/hooks/scribe_defs.sh
|
|
. "$(dirname "${BASH_SOURCE[0]}")/scribe_defs.sh"
|
|
|
|
command -v curl >/dev/null 2>&1 || exit 0
|
|
|
|
scribe_config || exit 0
|
|
|
|
body=$(curl -fsS --max-time 8 \
|
|
-H "Authorization: Bearer ${token}" \
|
|
"${url%/}/api/plugin/processes" 2>/dev/null) || exit 0
|
|
[ -n "$body" ] || exit 0
|
|
|
|
body_flat=$(printf '%s' "$body" | scribe_json_flat)
|
|
# The array's LENGTH, which is "" when `.processes` is not an array at all —
|
|
# a different answer from 0, and the one that means the response was not what
|
|
# this hook expects.
|
|
count=$(scribe_json_len "$body_flat" '.processes')
|
|
case "$count" in ''|*[!0-9]*) exit 0 ;; esac
|
|
|
|
skills_dir="${HOME}/.claude/skills"
|
|
mkdir -p "$skills_dir" 2>/dev/null || exit 0
|
|
|
|
# Slugs written this run — anything else under scribe-proc-* is pruned below.
|
|
managed=" "
|
|
|
|
i=0
|
|
while [ "$i" -lt "$count" ]; do
|
|
name=$(scribe_json_pick "$body_flat" ".processes[$i].name")
|
|
slug=$(scribe_json_pick "$body_flat" ".processes[$i].slug")
|
|
desc=$(scribe_json_pick "$body_flat" ".processes[$i].description")
|
|
i=$((i + 1))
|
|
[ -n "$slug" ] && [ -n "$name" ] || continue
|
|
|
|
dir="${skills_dir}/scribe-proc-${slug}"
|
|
mkdir -p "$dir" 2>/dev/null || continue
|
|
# description folded to one line — YAML scalar must not contain a newline.
|
|
desc=$(printf '%s' "$desc" | tr '\n' ' ')
|
|
{
|
|
printf -- '---\n'
|
|
printf 'name: scribe-proc-%s\n' "$slug"
|
|
printf 'description: %s\n' "$desc"
|
|
printf -- '---\n\n'
|
|
printf '<!-- GENERATED by the Grimoire plugin (scribe_sync_processes.sh). Do not edit here; edit the Process in Grimoire and re-sync with /scribe:sync. -->\n\n'
|
|
printf 'This is a saved **Grimoire Process**: `%s`.\n\n' "$name"
|
|
printf 'Do not improvise it. Call `get_process("%s")` via the Grimoire MCP server to fetch the live procedure, then follow the returned body verbatim — including any "clarify first" steps it contains.\n' "$name"
|
|
} > "${dir}/SKILL.md" 2>/dev/null || continue
|
|
managed="${managed}${slug} "
|
|
done
|
|
|
|
# Prune stubs whose Process no longer exists (scoped to our scribe-proc-* prefix).
|
|
for d in "${skills_dir}"/scribe-proc-*; do
|
|
[ -d "$d" ] || continue
|
|
slug=$(basename "$d"); slug=${slug#scribe-proc-}
|
|
case "$managed" in
|
|
*" $slug "*) : ;;
|
|
*) rm -rf "$d" 2>/dev/null ;;
|
|
esac
|
|
done
|
|
|
|
exit 0
|