#!/usr/bin/env node // stop-reminder.mjs // Stop hook for linkedin-thought-leadership plugin // // Only fires if LinkedIn content was worked on (session marker exists). // First stop: blocks with reason (Claude processes reminders). // Subsequent stops within 60s: allows (prevents infinite loop). // // Exit codes: // 0 - Allow (pass through or second stop) // 2 - Not used; uses {"decision": "block"} JSON instead import { readFileSync, writeFileSync, existsSync, statSync, unlinkSync, mkdirSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const pluginRoot = join(__dirname, '..', '..'); const promptFile = join(pluginRoot, 'hooks', 'prompts', 'state-update-reminder.md'); const sessionDir = '/tmp/linkedin-hooks'; const sessionMarker = join(sessionDir, 'session-active'); const lockFile = join(sessionDir, 'stop-hook.lock'); function nowSeconds() { return Date.now() / 1000; } function fileAgeSeconds(filePath) { try { return nowSeconds() - statSync(filePath).mtime.getTime() / 1000; } catch { return Infinity; } } function safeUnlink(filePath) { try { unlinkSync(filePath); } catch { /* ignore */ } } // Read stdin let input; try { input = JSON.parse(readFileSync(0, 'utf-8')); } catch { input = {}; } // Infinite loop prevention: if Claude is already continuing from a Stop hook if (input.stop_hook_active === true) { process.stdout.write('{}'); process.exit(0); } // No session marker = no LinkedIn work done if (!existsSync(sessionMarker)) { process.stdout.write('{}'); process.exit(0); } // Staleness check: ignore markers older than 12 hours (43200 seconds) if (fileAgeSeconds(sessionMarker) > 43200) { safeUnlink(sessionMarker); process.stdout.write('{}'); process.exit(0); } // Infinite-loop prevention: lock file within 60 seconds = second stop if (existsSync(lockFile)) { if (fileAgeSeconds(lockFile) < 60) { safeUnlink(lockFile); safeUnlink(sessionMarker); process.stdout.write('{}'); process.exit(0); } safeUnlink(lockFile); } // First stop: create lock and block with reminder prompt mkdirSync(sessionDir, { recursive: true }); writeFileSync(lockFile, ''); if (!existsSync(promptFile)) { process.stdout.write('{}'); process.exit(0); } const promptContent = readFileSync(promptFile, 'utf-8'); process.stdout.write(JSON.stringify({ decision: 'block', reason: promptContent })); process.exit(0);