Server (SvelteKit + SQLite Docker container) + per-machine agent that parses Claude Code JSONL transcripts. docker-compose one-command deploy; raw transcript view + search gated behind SHOW_TRANSCRIPTS (hidden by default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
3.6 KiB
Bash
Executable file
74 lines
3.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# toknmtr-capture.sh — Claude Code hook that triggers an incremental toknmtr agent sweep.
|
|
#
|
|
# Registered (by ops/install-hook.sh) under the Claude Code 'Stop' hook event, so it fires
|
|
# at the end of every assistant turn — see ops/install-hook.sh for the Stop-vs-SessionEnd
|
|
# tradeoff writeup.
|
|
#
|
|
# FAIL-OPEN CONTRACT — this script must NEVER block, slow down, or fail the Claude Code
|
|
# session it's attached to:
|
|
# - The actual agent run (`node agent/run.ts --once`) is launched with `nohup ... &` and
|
|
# disowned, with stdio redirected to a log file (never inherited from the hook's own
|
|
# stdio), so this script returns to Claude Code in a handful of milliseconds — it does
|
|
# NOT wait for the parse+push to finish.
|
|
# - `timeout` wraps the backgrounded agent run so a hung/unreachable server can never
|
|
# leave an orphaned process running forever.
|
|
# - Every prerequisite (project dir, config file, node, timeout) is individually checked;
|
|
# any miss just skips capture for this turn silently.
|
|
# - This script prints NOTHING to its own stdout/stderr (Claude Code parses Stop-hook
|
|
# stdout as potential JSON — e.g. {"decision":"block"} would force the session to keep
|
|
# going — so silence here is required, not just polite). All diagnostic output goes to
|
|
# $LOG_FILE instead.
|
|
# - Always exits 0, intentionally not using `set -e`: every step below is already
|
|
# individually guarded, so a failure anywhere means "skip capture this turn", never
|
|
# "fail the hook" / block the Stop event.
|
|
#
|
|
# Config: ~/.toknmtr/env (TOKNMTR_URL, TOKNMTR_TOKEN — see ops/README.md). Kept out of
|
|
# ~/.claude/settings.json so secrets aren't sitting in a file that's more likely to be
|
|
# shared, synced, or dumped for support/debugging.
|
|
#
|
|
# Override knobs (env, all optional):
|
|
# TOKNMTR_PROJECT_DIR path to the toknmtr repo checkout (default: ~/claude/projects/toknmtr)
|
|
# TOKNMTR_ENV_FILE path to the config file (default: ~/.toknmtr/env)
|
|
# TOKNMTR_LOG_FILE where backgrounded output is logged (default: ~/.toknmtr/capture.log)
|
|
# TOKNMTR_HOOK_TIMEOUT_S max seconds the backgrounded sweep may run (default: 25)
|
|
|
|
PROJECT_DIR="${TOKNMTR_PROJECT_DIR:-$HOME/claude/projects/toknmtr}"
|
|
CONFIG_FILE="${TOKNMTR_ENV_FILE:-$HOME/.toknmtr/env}"
|
|
LOG_FILE="${TOKNMTR_LOG_FILE:-$HOME/.toknmtr/capture.log}"
|
|
TIMEOUT_S="${TOKNMTR_HOOK_TIMEOUT_S:-25}"
|
|
|
|
# Claude Code hooks receive a JSON payload on stdin describing the event. We don't need its
|
|
# contents (the agent re-walks transcripts itself from disk), but drain it anyway so we
|
|
# never leave the pipe half-read.
|
|
cat >/dev/null 2>&1 || true
|
|
|
|
# Bail out quietly (still exit 0) if any prerequisite is missing — never surface a hook
|
|
# failure to the session over a merely-unconfigured machine.
|
|
[ -d "$PROJECT_DIR" ] || exit 0
|
|
[ -f "$PROJECT_DIR/agent/run.ts" ] || exit 0
|
|
[ -f "$CONFIG_FILE" ] || exit 0
|
|
command -v node >/dev/null 2>&1 || exit 0
|
|
|
|
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || exit 0
|
|
|
|
# Build the backgrounded command as a single string for `bash -c` so it can `cd`, source
|
|
# the config file (exporting TOKNMTR_URL/TOKNMTR_TOKEN into its own environment), and then
|
|
# exec node — all inside the detached child, never the foreground hook process.
|
|
read -r -d '' INNER_CMD <<EOF || true
|
|
cd "$PROJECT_DIR" || exit 0
|
|
set -a
|
|
. "$CONFIG_FILE"
|
|
set +a
|
|
exec node --experimental-strip-types agent/run.ts --once
|
|
EOF
|
|
|
|
if command -v timeout >/dev/null 2>&1; then
|
|
nohup timeout "${TIMEOUT_S}s" bash -c "$INNER_CMD" >>"$LOG_FILE" 2>&1 &
|
|
else
|
|
nohup bash -c "$INNER_CMD" >>"$LOG_FILE" 2>&1 &
|
|
fi
|
|
disown 2>/dev/null || true
|
|
|
|
exit 0
|