KeiSeiKit-1.0/install/lib-dev-hub-mdbook.sh
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

198 lines
7 KiB
Bash

# shellcheck shell=bash
# lib-dev-hub-mdbook.sh — install mdBook (Rust static-site doc-hub) via cargo + launchd.
#
# Renders every ~/Projects/<repo>/{CLAUDE,DECISIONS,RUNBOOK,DESIGN}.md as a
# single browsable web book served on http://127.0.0.1:7080/. Two launchd
# agents: `mdbook-server` (long-lived, --no-rebuild) and `mdbook-rebuilder`
# (timer, regenerates SUMMARY.md + chapters + `mdbook build` every 3600s).
#
# The rebuild logic is owned by ${KIT}/dev-hub/mdbook-rebuild.sh — emitted by
# write_rebuild_wrapper. regenerate_summary is a thin wrapper that invokes it
# synchronously at install time so the user sees the first render immediately.
#
# Sources: lib-log.sh (say/warn/err), lib-launchd.sh (install_service/unload_plist).
# Globals read: $KIT_DIR, $HOME_DIR.
# ---------- helpers (private) ----------
# Verify cargo is on PATH (rustup default stable). 0 OK, 1 missing.
_mdbook_check_cargo() {
if ! command -v cargo >/dev/null 2>&1; then
err "cargo not found — install via: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
return 1
fi
say " → cargo $(cargo --version 2>/dev/null) OK"
}
# Idempotent `cargo install mdbook`. Cargo no-ops if already at latest crate.
_mdbook_install_binary() {
say " → cargo install mdbook (no-op if already at latest)"
cargo install mdbook 2>&1 | grep -vE "^(\s*Compiling|\s*Finished|\s*Updating)" || true
}
# Render install/launchd-templates/book.toml.tmpl → ${DATA}/book.toml.
# Args: <data-dir>.
_mdbook_render_book_toml() {
local data_dir="$1"
local tmpl="$KIT_DIR/install/launchd-templates/book.toml.tmpl"
if [ ! -f "$tmpl" ]; then
err "book.toml.tmpl missing: $tmpl"
return 1
fi
mkdir -p "$data_dir"
sed \
-e "s|\${HOME}|${HOME_DIR}|g" \
-e "s|\${USER}|${USER}|g" \
-e "s|\${DATA}|${data_dir}|g" \
"$tmpl" > "$data_dir/book.toml"
say " → wrote $data_dir/book.toml"
}
# Emit ${KIT}/dev-hub/mdbook-serve.sh — wraps `mdbook serve` with binary discovery.
write_serve_wrapper() {
local wrapper="$HOME_DIR/.claude/agents/_primitives/dev-hub/mdbook-serve.sh"
mkdir -p "$(dirname "$wrapper")"
cat > "$wrapper" <<'WRAPPER_EOF'
#!/usr/bin/env bash
# Auto-generated by lib-dev-hub-mdbook.sh — do not edit by hand.
# Resolves mdbook binary (cargo / brew / PATH) and serves the rendered book.
set -eu
DATA="$HOME/Library/Application Support/keisei/mdbook"
MDBOOK="$(command -v mdbook || echo "$HOME/.cargo/bin/mdbook")"
[ -x "$MDBOOK" ] || { echo "mdbook not found (tried PATH and ~/.cargo/bin/mdbook)" >&2; exit 1; }
exec "$MDBOOK" serve "$DATA" \
--hostname 127.0.0.1 --port 7080 --no-rebuild
WRAPPER_EOF
chmod +x "$wrapper"
say " → wrote $wrapper"
}
# Emit ${KIT}/dev-hub/mdbook-rebuild.sh — regenerates src/ tree + `mdbook build`.
# Idempotent: every run wipes & rebuilds src/ from ~/Projects/.
write_rebuild_wrapper() {
local wrapper="$HOME_DIR/.claude/agents/_primitives/dev-hub/mdbook-rebuild.sh"
mkdir -p "$(dirname "$wrapper")"
cat > "$wrapper" <<'WRAPPER_EOF'
#!/usr/bin/env bash
# Auto-generated by lib-dev-hub-mdbook.sh — do not edit by hand.
# Regenerate SUMMARY.md + chapters from ~/Projects/ + rebuild book.
set -e
DATA="$HOME/Library/Application Support/keisei/mdbook"
LOGS="$HOME/Library/Logs/keisei/mdbook"
mkdir -p "$DATA/src" "$LOGS"
# Helper: slugify project name (lowercase, non-alphanum → '-', collapse, trim).
slugify() {
echo "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//'
}
# Find mdbook binary (cargo or brew).
MDBOOK="$(command -v mdbook || echo "$HOME/.cargo/bin/mdbook")"
[ -x "$MDBOOK" ] || { echo "mdbook not found" >&2; exit 1; }
# Wipe + regen src tree (chapter content only — book.toml stays at $DATA/book.toml).
rm -rf "$DATA/src"
mkdir -p "$DATA/src"
SUMMARY="$DATA/src/SUMMARY.md"
echo "# Summary" > "$SUMMARY"
echo "" >> "$SUMMARY"
for proj in "$HOME/Projects"/*/; do
[ -d "$proj.git" ] || continue
name="$(basename "$proj")"
slug="$(slugify "$name")"
chapter_dir="$DATA/src/$slug"
mkdir -p "$chapter_dir"
if [ -f "$proj/CLAUDE.md" ]; then
cp "$proj/CLAUDE.md" "$chapter_dir/index.md"
elif [ -f "$proj/README.md" ]; then
cp "$proj/README.md" "$chapter_dir/index.md"
else
# No CLAUDE.md and no README.md → skip empty chapter.
rmdir "$chapter_dir" 2>/dev/null || true
continue
fi
echo "- [$name](./$slug/index.md)" >> "$SUMMARY"
for doc in DECISIONS.md RUNBOOK.md DESIGN.md; do
if [ -f "$proj/$doc" ]; then
lower="$(echo "${doc%.md}" | tr '[:upper:]' '[:lower:]')"
cp "$proj/$doc" "$chapter_dir/${lower}.md" 2>/dev/null || true
echo " - [${doc%.md}](./$slug/${lower}.md)" >> "$SUMMARY"
fi
done
done
"$MDBOOK" build "$DATA" 2>>"$LOGS/rebuilder.err.log"
echo "rebuilt at $(date -u +%FT%TZ)"
WRAPPER_EOF
chmod +x "$wrapper"
say " → wrote $wrapper"
}
# ---------- public API ----------
# Walk ~/Projects/ and (re)generate ${DATA}/src/* + SUMMARY.md.
# Delegates to mdbook-rebuild.sh so install-time and timer use identical logic.
regenerate_summary() {
local rebuild="$HOME_DIR/.claude/agents/_primitives/dev-hub/mdbook-rebuild.sh"
if [ ! -x "$rebuild" ]; then
err "mdbook-rebuild.sh not found — call write_rebuild_wrapper first"
return 1
fi
say " → regenerating chapter tree from $HOME_DIR/Projects/"
"$rebuild" >/dev/null
}
# Install mdBook + render config + first build + register both launchd agents.
# Idempotent.
install_dev_hub_mdbook() {
say "installing dev-hub-mdbook"
_mdbook_check_cargo || return 1
_mdbook_install_binary
local data_dir="$HOME_DIR/Library/Application Support/keisei/mdbook"
mkdir -p "$data_dir/src" "$data_dir/book"
_mdbook_render_book_toml "$data_dir" || return 1
write_serve_wrapper
write_rebuild_wrapper
regenerate_summary || return 1
# shellcheck source=./lib-launchd.sh
. "$KIT_DIR/install/lib-launchd.sh"
install_service mdbook-server
install_service mdbook-rebuilder
say "mdBook served on http://127.0.0.1:7080/. Rebuilds every 1h."
}
# Unload + remove both plists. Optional --purge also `cargo uninstall mdbook`.
uninstall_dev_hub_mdbook() {
say "uninstalling dev-hub-mdbook"
# shellcheck source=./lib-launchd.sh
. "$KIT_DIR/install/lib-launchd.sh"
unload_plist mdbook-server
unload_plist mdbook-rebuilder
rm -f "$HOME_DIR/.claude/agents/_primitives/dev-hub/mdbook-serve.sh"
rm -f "$HOME_DIR/.claude/agents/_primitives/dev-hub/mdbook-rebuild.sh"
if [ "${1:-}" = "--purge" ] && command -v cargo >/dev/null 2>&1; then
say " → cargo uninstall mdbook"
cargo uninstall mdbook || true
fi
}
# Verify mdBook server returns 200 on http://127.0.0.1:7080/. 0 OK, 1 fail.
verify_dev_hub_mdbook() {
local code
code="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:7080/ 2>/dev/null || echo 000)"
if [ "$code" = "200" ]; then
say "mdbook server health OK (200)"
return 0
fi
err "mdbook server health failed (HTTP $code) — check logs at $HOME_DIR/Library/Logs/keisei/mdbook-server/"
return 1
}