From 2e8c8acced83698f3c7b0f95fb73df70754a0eaa Mon Sep 17 00:00:00 2001 From: Parfii-bot Date: Tue, 21 Apr 2026 19:43:21 +0800 Subject: [PATCH] feat(hooks): tomd-preread PreToolUse(Read) auto-convert hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POSIX sh hook (50 LOC) that intercepts Read on .docx/.doc/.xlsx/.pptx/.csv and auto-redirects Claude to a cached markdown conversion via the tomd primitive. - jq absence → exit 0 (graceful degrade, matches assemble-validate style) - tomd primitive missing → exit 0 (don't block) - cache dir via KEISEI_TOMD_CACHE env (default /tmp/keisei-tomd-cache) - cache key = basename + mtime, portable stat for macOS/Linux - exit 2 with [tomd-preread] stderr message on successful conversion - conversion failure → exit 0 (let Claude try original, fail naturally) Not wired into settings-snippet.json yet — follow-up commit adds the PreToolUse(Read) entry alongside install.sh hooks-copy loop extension. Co-Authored-By: Claude Opus 4.7 (1M context) --- hooks/tomd-preread.sh | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 hooks/tomd-preread.sh diff --git a/hooks/tomd-preread.sh b/hooks/tomd-preread.sh new file mode 100755 index 0000000..e8ceb17 --- /dev/null +++ b/hooks/tomd-preread.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# PreToolUse(Read) — auto-convert non-native formats to markdown and redirect +# Claude to read the converted file instead of the opaque binary. +# +# Exit 0 = allow (passthrough). Exit 2 = block with stderr message (Claude +# reads the stderr text and switches to the converted path). +# +# Stdin: JSON with tool_input.file_path. + +# Silent fall-through if jq is absent; otherwise `set -eu` would abort and +# Claude Code would refuse Read system-wide. +command -v jq >/dev/null 2>&1 || exit 0 + +set -eu + +TOMD="$HOME/.claude/agents/_primitives/tomd.sh" +CACHE_DIR="${KEISEI_TOMD_CACHE:-/tmp/keisei-tomd-cache}" + +FILE=$(jq -r '.tool_input.file_path // empty') +[ -n "$FILE" ] || exit 0 +[ -f "$FILE" ] || exit 0 + +# Extension whitelist — only these formats trigger conversion. +EXT=$(printf '%s' "${FILE##*.}" | tr '[:upper:]' '[:lower:]') +case "$EXT" in + docx|doc|xlsx|pptx|csv) ;; + *) exit 0 ;; +esac + +# tomd primitive must be installed; if absent, don't block the Read. +[ -x "$TOMD" ] || exit 0 + +mkdir -p "$CACHE_DIR" + +# Cache key: basename + mtime. Portable stat for macOS + Linux. +BASENAME=$(basename "$FILE") +MTIME=$(stat -f %m "$FILE" 2>/dev/null || stat -c %Y "$FILE" 2>/dev/null || echo 0) +MD_FILE="$CACHE_DIR/${BASENAME%.*}-${MTIME}.md" + +if [ ! -s "$MD_FILE" ]; then + "$TOMD" "$FILE" > "$MD_FILE" 2>/dev/null || true +fi + +if [ -s "$MD_FILE" ]; then + echo "[tomd-preread] Auto-converted to markdown: $MD_FILE. Use Read on $MD_FILE instead of $FILE." >&2 + exit 2 +fi + +# Conversion failed or produced empty output — degrade gracefully. +exit 0