Claude Code
Skills & Hooks

Browser Automation, AI Commits, Lifecycle Hooks

Markus Klug

German IT Entrepreneur since 1997, now based in Lisbon.
Award-Winning Founder and Builder, FinTech Veteran,
Agentic Coding Pioneer.

What Are Skills?

Folders that extend Claude with on-demand capabilities

A Skill is a Folder

~/.claude/skills/browser/ SKILL.md # instructions start.js # launch Chrome click.js # click elements dom.js # inspect DOM screenshot.js # capture page ... # 14 tools total node_modules/ # dependencies

SKILL.md Frontmatter

--- name: browser description: Browser automation via Chrome CDP. Use when the user asks to open a page, click elements... allowed-tools: - Bash - Read ---

Scripts & assets

Not just markdown

Tool restrictions

allowed-tools limits scope

Model selection

model: haiku, sonnet, opus

/browser

14 CLI tools for Chrome automation via CDP

Script Usage Purpose
start.jsnode start.jsLaunch Chrome with CDP on :9222
nav.jsnode nav.js <url|back|forward|reload>Navigate
dom.jsnode dom.js [--selector] [--depth] [--a11y]DOM / accessibility tree
click.jsnode click.js <selector> [--double] [--right]Click elements
type.jsnode type.js <selector> 'text' [--clear]Type into inputs
screenshot.jsnode screenshot.js [--full] [--selector]Capture page
scroll.jsnode scroll.js [--up|--down] [--amount]Scroll page / element
eval.jsnode eval.js '<code>'Run JS in page
console.jsnode console.js [--filter] [--clear]Console messages
network.jsnode network.js [--filter]Network snapshot
wait.jsnode wait.js <selector|text> [--gone]Wait for condition
hover.jsnode hover.js <selector>Hover over element
select.jsnode select.js <selector> <value>Dropdown selection
tabs.jsnode tabs.js <list|switch|close>Tab management

Prefer dom.js over screenshots. The DOM tree is faster and more informative than pixel data.

Browser: start.js

Launch Chrome stable with CDP — reuse real cookies

// 1. Reuse existing Chrome if CDP responds try { await fetch("http://localhost:9222/json/version"); process.exit(0); } catch {} // 2. Kill Chrome stable (Beta unaffected) execSync("killall 'Google Chrome'"); // 3. Copy auth files from real profile const authFiles = [ "Cookies", "Login Data", "Web Data" // + journals ]; for (const f of authFiles) { cp(realProfile/f, skillProfile/f); } // 4. Launch with CDP on :9222 spawn("Google Chrome", [ `--remote-debugging-port=9222`, `--user-data-dir=${dataDir}` ]);

Key Design Decisions

1.

Idempotent start — checks if CDP is already running, exits early

2.

Chrome stable only — kills stable, leaves Beta untouched

3.

Cookie sync — copies auth files from real profile so you're logged into everything

4.

Dedicated data dir — CDP won't enable on Chrome's default dir

One dependency: playwright-core

All scripts connect to CDP via playwright-core's chromium.connectOverCDP()

Live Demo

/browser in action

1

start.js — launch Chrome with CDP

2

nav.js — navigate to a page

3

dom.js --a11y — inspect the accessibility tree

4

click.js / type.js — interact with elements

5

screenshot.js — visual verification

/git-staged

AI-Powered Commit Messages

Two Variants * Headless Pipeline * tmux Popup Review

Two Variants, One Goal

Interactive vs headless — same commit guidelines, different UX

/git-staged

Interactive — runs inside Claude conversation

model: claude-haiku-4-5 disable-model-invocation: true allowed-tools: - Bash - AskUserQuestion

Runs gather-context script

Presents message via AskUserQuestion

Options: Commit / Push / Edit / Cancel

/git-staged-headless

Background — runs outside Claude, pops up in tmux

model: claude-haiku-4-5 disable-model-invocation: true # no allowed-tools # no tool calls at all

Receives diff via stdin pipe

Outputs raw commit message text only

Review happens in tmux popup via gum

Both use Haiku for cost efficiency. Both share the same commit message guidelines.

The Headless Pipeline

Two phases: background AI call, then interactive review

Phase 1: Background

# Gather context DIFF=$(git diff --cached) RECENT=$(git log -n 15) # Truncate if > 80k chars if [[ $DIFF_LEN -gt 80000 ]]; then # include stat summary STAT=$(git diff --cached --stat) fi # Call Claude headless MSG=$(echo "$CONTEXT" | \ claude -p /git-staged-headless) echo "$MSG" > $TMPFILE
->

Phase 2: tmux Popup

# Spawn interactive popup tmux display-popup -E \ -w 80% -h 60% \ "run-review $TMPFILE $WORKDIR" # Inside run-review: MSG=$(cat "$TMPFILE") echo "Proposed commit message:" echo "$MSG" # gum interactive chooser ACTION=$(gum choose \ "Commit" \ "Commit & Push" \ "Edit message" \ "Cancel")

Non-blocking

Runs entirely in background with ( ... ) & disown

Smart truncation

Large diffs get stat summary + truncated diff

Zero-tool skill

SKILL.md forbids all tools — pure text in, text out

Live Demo

/git-staged-headless in action

1

Stage some changes with git add

2

Run ~/.claude/skills/git-staged-headless/run

3

tmux shows "Generating commit message..."

4

Popup appears with proposed message + gum chooser

5

Choose: Commit / Commit & Push / Edit / Cancel

Hooks

Making Claude Self-Aware

tmux State * Auto-Format * Auto-Approve * HTTP Dashboard

18 Lifecycle Events

Shell commands, HTTP endpoints, LLM prompts, or agents — fired at every stage

*
SessionStart
Session begins or resumes
B
UserPromptSubmit
Before Claude processes prompt
*
InstructionsLoaded
CLAUDE.md or rules file loaded
B
PreToolUse
Before a tool call executes
*
PostToolUse
After a tool call succeeds
*
PostToolUseFailure
After a tool call fails
B
PermissionRequest
Permission dialog appears
B
Stop
Claude finishes responding
*
Notification
Claude sends a notification
*
SubagentStart
Subagent spawned
B
SubagentStop
Subagent finishes
B
TeammateIdle
Teammate about to go idle
B
TaskCompleted
Task marked as completed
B
ConfigChange
Config file changes mid-session
*
PreCompact
Before context compaction
B
WorktreeCreate
Worktree being created
*
WorktreeRemove
Worktree being removed
*
SessionEnd
Session terminates
B = can block / modify * = side-effects only | 4 hook types: command, HTTP, prompt, agent

My Hooks Setup

5 shell scripts wired to 18 events in ~/.claude/settings.json

tmux-state.sh

Window title reflects Claude's state

UserPromptSubmit -> "cc1 ●"
PermissionRequest -> "cc1 ◆"
Stop / SessionEnd -> "cc1"

dprint-fmt-queue.sh + dprint-fmt-run.sh

Queue-and-flush auto-formatting

PostToolUse(Edit|Write) -> queue file path
Stop -> dprint fmt each queued file

auto-approve-claude-settings.sh

Claude can edit its own config without asking

PreToolUse(Edit|Write)
if path contains /.claude/
-> permissionDecision: "allow"

HTTP Dashboard

Every event streams to localhost:8002

All 18 events -> POST /api/hooks
Mix of type: "http" and curl

5 scripts + 1 HTTP endpoint = full observability over every Claude session

tmux-state.sh

31 lines that make Claude visible in your terminal

#!/bin/bash STATE="$1" [ -z "$TMUX_PANE" ] && exit 0 # Get current window name, strip old state FULL=$(tmux display-message \ -t "$TMUX_PANE" -p '#{window_name}') BASE=$(echo "$FULL" | \ sed 's/ [●◆]$//' | sed 's/:.*//') # Read optional topic TOPIC_FILE="/tmp/claude-topic-${TMUX_PANE}" [ -f "$TOPIC_FILE" ] && \ TOPIC=$(cat "$TOPIC_FILE") # Compose and rename case "$STATE" in running) "$DISPLAY ●" ;; needs-input) "$DISPLAY ◆" ;; idle) "$DISPLAY" ;; esac

What You See in tmux

cc1:auth-fix
Claude is working
cc1:auth-fix
Needs your attention
cc1
Idle / done

Why This Matters

Running 3 Claude sessions in parallel?

Glance at tmux tab bar to see which needs input.

No context switching. No guessing.

Auto-Format Pipeline

Queue on edit, flush on stop — never interrupts Claude

dprint-fmt-queue.sh

PostToolUse hook — matcher: Edit|Write

INPUT=$(cat) FILE_PATH=$(echo "$INPUT" | \ jq -r '.tool_input.file_path') SESSION_ID=$(echo "$INPUT" | \ jq -r '.session_id') case "$FILE_PATH" in *.md|*.css|*.ts|*.tsx) QUEUE="/tmp/dprint-fmt-queue-${SESSION_ID}" if ! grep -qxF "$FILE_PATH" "$QUEUE"; then echo "$FILE_PATH" >> "$QUEUE" fi ;; esac

dprint-fmt-run.sh

Stop hook — runs when Claude finishes a turn

INPUT=$(cat) SESSION_ID=$(echo "$INPUT" | \ jq -r '.session_id') QUEUE="/tmp/dprint-fmt-queue-${SESSION_ID}" [ ! -f "$QUEUE" ] && exit 0 sort -u "$QUEUE" | \ while IFS= read -r file; do if [ -f "$file" ]; then dprint fmt "$file" fi done rm -f "$QUEUE"

Session-scoped queue

Each session has its own temp file

Deduplication

grep -qxF prevents duplicate entries

Clean cleanup

Queue file removed after flush

Auto-Approve Hook

Claude manages its own .claude/ config without prompting

auto-approve-claude-settings.sh

PreToolUse hook — matcher: Edit|Write

INPUT=$(cat) FILE_PATH=$(echo "$INPUT" | \ jq -r '.tool_input.file_path') # Auto-approve if path contains /.claude/ if echo "$FILE_PATH" | \ grep -q '/\.claude/'; then echo '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Auto-approved: .claude/ path" } }' exit 0 fi # Fall through — don't interfere echo '{}'

Why This Exists

Claude frequently updates its own skills, memory, and settings files inside ~/.claude/.

Without this hook, every edit to a skill or memory file would trigger a permission prompt.

This hook auto-approves only paths containing /.claude/ — everything else falls through to normal permission handling.

PreToolUse: The Gatekeeper

PreToolUse hooks can allow, deny, or pass through.

Return empty {} to not interfere.

This is the only blocking hook type that can override permissions programmatically.

The Wiring

How hooks connect to events in settings.json

"hooks": { "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "tmux-state.sh running" }, { "type": "http", "url": "http://localhost:8002/api/hooks" }] }], "PermissionRequest": [{ "hooks": [{ "type": "command", "command": "tmux-state.sh needs-input" }] }], "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "dprint-fmt-queue.sh" }] }],
"PreToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "auto-approve-claude-settings.sh" }] }], "Stop": [{ "hooks": [{ "type": "command", "command": "dprint-fmt-run.sh" }, { "type": "command", "command": "tmux-state.sh idle" }, { "type": "http", "url": "http://localhost:8002/api/hooks" }] }], // + SessionStart, SessionEnd, SubagentStart, // SubagentStop, TeammateIdle, PreCompact, // InstructionsLoaded, ConfigChange, // WorktreeCreate, WorktreeRemove // → all stream to HTTP dashboard }

matcher: "Edit|Write"

Filter hooks by tool name — regex supported

Hook input via stdin

Every hook receives JSON context: session_id, tool_input, etc.

Recap

Three patterns for extending Claude Code

/browser

Skill as a tool suite

14 CLI scripts + playwright-core

CDP-based Chrome automation

DOM-first observation strategy

Skills can ship real code

/git-staged-headless

Skill as a headless API

Zero tools, pure text pipeline

Background Claude + tmux popup

gum-based interactive review

Skills can be pipelines

Hooks

5 shell scripts, 18 events

tmux state indicators

Queue-and-flush auto-formatting

Programmatic permission control

Hooks make Claude aware

Claude Code is a development platform — skills and hooks are the extension API

Thank You

LinkedIn QR

Markus Klug

@opus131
Questions?