// Hero + install block + real ClooksHook snippet function InstallBlock({ cmd, accent, autoType = true, lines = [] }) { // Three-step install: add marketplace → install+enable plugin → scaffold const steps = [ { cmd, output: [ ['→ Added marketplace ', ['muted', 'codestripes-dev/clooks-marketplace']], ], doneLabel: '✓ added.', typeSpeed: 12, runMs: 500, }, { cmd: 'claude plugin install clooks@clooks-marketplace', output: [ ['→ Installed ', ['muted', 'clooks@1.2.0'], ', enabled in this project.'], ], doneLabel: '✓ enabled.', typeSpeed: 12, runMs: 500, }, { cmd: '/clooks:setup', output: [ ['→ Created ', ['code', '.clooks/clooks.yml'], ', ', ['code', '.clooks/hooks/'], ', ', ['code', '.clooks/lockfile.json']], ['→ Installed ', ['muted', 'clooks-core-hooks@1.2.0'], ' (3 hooks)'], ], doneLabel: '✓ ready.', typeSpeed: 14, runMs: 600, }, ]; const [copied, setCopied] = React.useState(false); // For each step: { typed: string, phase: 'idle'|'typing'|'running'|'done' } const [state, setState] = React.useState( steps.map((s, i) => ({ typed: autoType ? '' : s.cmd, phase: autoType ? (i === 0 ? 'typing' : 'idle') : 'done' })) ); const setStep = React.useCallback((i, patch) => { setState(prev => prev.map((s, idx) => idx === i ? { ...s, ...patch } : s)); }, []); // Drive each step's typing + running React.useEffect(() => { if (!autoType) return; const active = state.findIndex(s => s.phase === 'typing'); if (active === -1) return; const { cmd: c, typeSpeed, runMs } = steps[active]; let i = state[active].typed.length; const iv = setInterval(() => { i++; setStep(active, { typed: c.slice(0, i) }); if (i >= c.length) { clearInterval(iv); setTimeout(() => setStep(active, { phase: 'running' }), 300); setTimeout(() => { setStep(active, { phase: 'done' }); if (active + 1 < steps.length) { setTimeout(() => setStep(active + 1, { phase: 'typing' }), 500); } }, 300 + runMs); } }, typeSpeed); return () => clearInterval(iv); // eslint-disable-next-line react-hooks/exhaustive-deps }, [state.map(s => s.phase).join(',')]); const copy = async () => { const parts = steps.map((s, i) => i === steps.length - 1 ? `claude ${s.cmd}` : s.cmd); const text = parts.join(' && '); let ok = false; try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); ok = true; } } catch {} if (!ok) { // Fallback for sandboxed iframes where clipboard API is blocked try { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px'; ta.setAttribute('readonly', ''); document.body.appendChild(ta); ta.select(); ok = document.execCommand('copy'); document.body.removeChild(ta); } catch {} } if (ok) { setCopied(true); setTimeout(() => setCopied(false), 1800); } }; return (
Write hooks once, run them safely, share them across projects and teams. Clooks wraps Claude Code's native hooks — so a crashed hook blocks the action instead of silently passing through.
Write hooks once, run them safely, share them across projects and teams. Clooks wraps native hooks so a crash blocks — it doesn't pass through.