KeiSeiKit-1.0/install/lib-dev-hub-datasette.sh
Parfii-bot a4e667de10 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

172 lines
5.8 KiB
Bash

# shellcheck shell=bash
# lib-dev-hub-datasette.sh — install Datasette (SQLite web UI) via pipx + launchd.
#
# Datasette serves any SQLite file as an interactive web UI + JSON API.
# We install via pipx (isolated venv, no system Python pollution) and run as
# a user-level launchd agent on http://127.0.0.1:8001/.
#
# READ-ONLY by design: every DB is opened with `--immutable` so a stray UI
# action cannot mutate the user's project databases.
#
# Architecture:
# plist (datasette.plist.tmpl) → ${KIT}/dev-hub/datasette-serve.sh wrapper
# wrapper discovers SQLite DBs at launch time → exec datasette serve
# This way newly-added DBs are picked up on next launchctl kickstart, with
# no plist rewrite needed.
#
# Sources: lib-log.sh (say/warn/err), lib-launchd.sh (install_service/unload_plist).
# Globals read: $KIT_DIR, $HOME_DIR.
# ---------- helpers (private) ----------
# Verify Python 3.11+. Returns 0 on OK, 1 on missing/old.
_datasette_check_python() {
if ! command -v python3 >/dev/null 2>&1; then
err "python3 not found — install via: brew install python@3.11"
return 1
fi
local ver
ver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)"
local major="${ver%.*}"
local minor="${ver#*.}"
if [ "$major" -lt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -lt 11 ]; }; then
err "python3 $ver too old (need 3.11+) — install via: brew install python@3.11"
return 1
fi
say " → python3 $ver OK"
}
# Ensure pipx is installed; if missing, install via brew.
_datasette_ensure_pipx() {
if command -v pipx >/dev/null 2>&1; then
say " → pipx $(pipx --version 2>/dev/null) already installed"
return 0
fi
if ! command -v brew >/dev/null 2>&1; then
err "neither pipx nor brew found — install Homebrew first: https://brew.sh"
return 1
fi
say " → installing pipx via brew"
brew install pipx
pipx ensurepath
}
# Write the runtime wrapper script that discovers DBs and execs datasette.
# Args: <wrapper-path>.
_datasette_write_wrapper() {
local wrapper="$1"
mkdir -p "$(dirname "$wrapper")"
cat > "$wrapper" <<'WRAPPER_EOF'
#!/usr/bin/env bash
# Auto-generated by lib-dev-hub-datasette.sh — do not edit by hand.
# Discovers SQLite DBs at launch time and serves them via Datasette (read-only).
set -eu
DBS=()
for f in "$HOME/.claude/agents"/*.sqlite "$HOME/Projects"/*/*.sqlite; do
[ -f "$f" ] && DBS+=("$f")
done
META="$HOME/Library/Application Support/keisei/datasette/metadata.yaml"
# Empty-array expansion is unsafe under `set -u` on bash 3.2 (macOS); branch.
if [ "${#DBS[@]}" -eq 0 ]; then
exec "$HOME/.local/bin/datasette" serve \
--host 127.0.0.1 --port 8001 \
--metadata "$META"
else
exec "$HOME/.local/bin/datasette" serve \
--host 127.0.0.1 --port 8001 \
--metadata "$META" \
--immutable "${DBS[@]}"
fi
WRAPPER_EOF
chmod +x "$wrapper"
}
# Write metadata.yaml if not already present (single-user, read-only defaults).
# Args: <data-dir>.
_datasette_write_metadata() {
local data_dir="$1"
local meta="$data_dir/metadata.yaml"
if [ -f "$meta" ]; then
say " → metadata.yaml already present (preserving user edits)"
return 0
fi
mkdir -p "$data_dir"
cat > "$meta" <<'META_EOF'
title: KeiSeiKit DBs
description: |
Read-only browser for KeiSeiKit SQLite databases (kei-ledger, kei-memory,
projects-index, etc.) and any project DB under ~/Projects/. Served by
Datasette in --immutable mode; no UI action can mutate these files.
allow_facet: true
allow_download: false
default_page_size: 100
META_EOF
say " → wrote $meta"
}
# ---------- public API ----------
# List SQLite paths to expose to Datasette.
# Walks: ~/.claude/agents/*.sqlite + ~/Projects/*/*.sqlite (depth 2).
# Output: newline-separated paths on stdout.
discover_databases() {
local f
for f in "$HOME_DIR/.claude/agents"/*.sqlite "$HOME_DIR/Projects"/*/*.sqlite; do
[ -f "$f" ] && printf '%s\n' "$f"
done
}
# Install Datasette + plugins + launchd service. Idempotent.
install_dev_hub_datasette() {
say "installing dev-hub-datasette"
_datasette_check_python || return 1
_datasette_ensure_pipx || return 1
say " → pipx install datasette"
pipx install datasette 2>&1 | grep -v "already seems to be installed" || true
say " → injecting plugins (cluster-map, vega, render-markdown)"
pipx inject datasette \
datasette-cluster-map \
datasette-vega \
datasette-render-markdown 2>&1 | grep -v "already" || true
local data_dir="$HOME_DIR/Library/Application Support/keisei/datasette"
_datasette_write_metadata "$data_dir"
local wrapper="$HOME_DIR/.claude/agents/_primitives/dev-hub/datasette-serve.sh"
_datasette_write_wrapper "$wrapper"
say " → wrote wrapper $wrapper"
# shellcheck source=./lib-launchd.sh
. "$KIT_DIR/install/lib-launchd.sh"
install_service datasette
say "Datasette running on http://127.0.0.1:8001/. Browse SQLite databases from the cortex-ui dashboard."
}
# Unload + remove plist. Optional --purge also removes the pipx install.
# Args: [--purge]
uninstall_dev_hub_datasette() {
say "uninstalling dev-hub-datasette"
# shellcheck source=./lib-launchd.sh
. "$KIT_DIR/install/lib-launchd.sh"
unload_plist datasette
rm -f "$HOME_DIR/.claude/agents/_primitives/dev-hub/datasette-serve.sh"
if [ "${1:-}" = "--purge" ] && command -v pipx >/dev/null 2>&1; then
say " → pipx uninstall datasette"
pipx uninstall datasette || true
fi
}
# Verify Datasette health endpoint returns 200. Returns 0 on OK, 1 on fail.
verify_dev_hub_datasette() {
local code
code="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8001/-/health 2>/dev/null || echo 000)"
if [ "$code" = "200" ]; then
say "datasette health OK (200)"
return 0
fi
err "datasette health failed (HTTP $code) — check logs at $HOME_DIR/Library/Logs/keisei/datasette/"
return 1
}