npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@radware/claude-code-hooks

v0.1.0

Published

No-code Claude Code hooks that forward raw lifecycle/tool events to a Radware LLM Protection ingest endpoint.

Readme

@radware/claude-code-hooks

No-code Claude Code → Radware LLM Protection event forwarding. This package installs Claude Code's native HTTP hooks so every lifecycle event POSTs its raw JSON to your Radware ingest endpoint, with the API key in an Authorization header. No scripts run per tool call, and nothing needs updating when Claude changes its payload format — only the backend redeploys.

Quickstart

npx @radware/claude-code-hooks install --name "Your Name"
# you'll be prompted for your Radware API key (hidden input)

npx downloads and runs the installer without leaving the npm package on your system. The installer itself writes hook entries permanently into ~/.claude/settings.json — they persist across sessions until you run uninstall.

Then launch Claude Code as usual. Set or rotate the key later with set-key; change your name with set-name.

To point at a staging or local endpoint instead of production, pass --url:

npx @radware/claude-code-hooks install --name "Your Name" --url https://your-host/llmp/digester/claude-code

What it installs

Into ~/.claude/settings.json (user-global — captures all of your Claude Code activity, every project):

  • 5 http hook entriesSessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop. Each POSTs the raw event to your URL with Authorization: Bearer ${RADWARE_AGENTIC_PROTECTION_AGENT_KEY}, X-Radware-User-Name: ${RADWARE_AGENTIC_PROTECTION_USER_NAME}, and timeout: 3.
  • env.RADWARE_AGENTIC_PROTECTION_AGENT_KEY — your key, and env.RADWARE_AGENTIC_PROTECTION_USER_NAME — your name. Claude injects and interpolates both into the headers (declared in each hook's allowedEnvVars). A name is required at install time: Claude Code sends no user identity, so this is how events are attributed in the Radware UI.

Commands

| Command | What it does | |---|---| | install [--url U] [--key K] [--name N] [--no-prompt] | register the hooks + store the key and name (name required) | | set-key [--key K] | set/rotate the key (hidden prompt, or pipe via stdin) | | set-name [--name N] | set/change the user name (prompt if omitted) | | uninstall [--url U] [--purge] | remove our hooks (--purge also deletes the key + name) |

CLAUDE_CONFIG_DIR overrides which .claude directory is touched (used for testing).

Backend contract

Claude Code interprets a hook's HTTP response:

  • empty 200 → clean no-op (fire-and-forget)
  • JSON body with decision / permissionDecisionblocks the tool call
  • plain-text body → injected into Claude's context
  • non-2xx or unreachable → non-blocking error, the session continues

Radware LLM Protection uses this as follows:

  • Monitoring (report-only) agents — the backend answers every event with an empty 200 and runs detection out of band. Strictly fire-and-forget; a down endpoint never breaks a session (it only drops events for the outage — there is no local buffer — and adds up to timeout seconds per event if it hangs).
  • Blocking agents — the backend answers PreToolUse synchronously with a permissionDecision: "deny" body to stop a disallowed tool from running; all other events stay fire-and-forget. Because this round-trips before the tool runs, give blocking agents more headroom than the default timeout: 3 (Claude proceeds with the tool if the hook times out). Tune TIMEOUT in lib/config.js.

What's captured

Forwarded raw: session starts, user prompts, assistant message text, and tool calls (input) + results (output). Every payload self-identifies via hook_event_name; the backend parses and stitches by session_id (one Claude Code session = one conversation).

The assistant's response text arrives on the Stop event via its last_assistant_message field (the final assistant message of the turn) — no transcript file reading and no per-tool scripts required. Intermediate assistant messages and thinking are not captured; prompts, the final answer, and tool activity are.

Payloads your backend receives

Each hook POSTs the raw Claude Code event JSON as the request body, unwrapped — there is no envelope. The API key is in the Authorization: Bearer … header, not in the body. Route on the hook_event_name field (present in every payload); the backend does all parsing and stitching (e.g. grouping by session_id).

The shapes below reflect Claude Code's current version and are illustrative, not a frozen contract — Claude Code adds and renames fields across releases. Parse defensively: route on hook_event_name, read the fields you need, ignore unknown ones. In particular tool_input / tool_response are tool-specific and open-ended — store them as raw JSON rather than modeling one fixed schema.

Common fields (every event): session_id, transcript_path, cwd, hook_event_name, and usually permission_mode ("default" | "plan" | "acceptEdits" | "auto" | "dontAsk" | "bypassPermissions").

1. SessionStart

{
  "session_id": "abc123",
  "transcript_path": "/home/you/.claude/projects/-home-you-myproj/abc123.jsonl",
  "cwd": "/home/you/myproj",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-6"
}

Session beginning. source"startup" | "resume" | "clear" | "compact". model present; agent_type / session_title optional.

2. UserPromptSubmit

{
  "session_id": "abc123",
  "transcript_path": "/home/you/.claude/projects/-home-you-myproj/abc123.jsonl",
  "cwd": "/home/you/myproj",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "List the files here and summarize the README"
}

prompt is the exact text the user submitted.

3. PreToolUse

{
  "session_id": "abc123",
  "transcript_path": "/home/you/.claude/projects/-home-you-myproj/abc123.jsonl",
  "cwd": "/home/you/myproj",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "ls -la", "description": "List files" }
}

Fires before a tool runs. tool_name + tool_input (shape varies by tool). This is the one event a blocking backend can answer with permissionDecision: "deny".

4. PostToolUse

{
  "session_id": "abc123",
  "transcript_path": "/home/you/.claude/projects/-home-you-myproj/abc123.jsonl",
  "cwd": "/home/you/myproj",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "ls -la", "description": "List files" },
  "tool_response": { "type": "text", "text": "total 8\ndrwxr-xr-x  4 you you ... README.md" }
}

Fires after a tool runs — the one event that carries both the tool input and its output (tool_response; some versions use tool_output).

5. Stop

{
  "session_id": "abc123",
  "transcript_path": "/home/you/.claude/projects/-home-you-myproj/abc123.jsonl",
  "cwd": "/home/you/myproj",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": false,
  "last_assistant_message": "Here are the files, and a summary of the README ..."
}

Marks the end of an assistant turn. Carries last_assistant_message — the full text of the final assistant message — which is how the backend records Claude's answer (no transcript parsing). May also carry effort or stop_hook_active.

tool_input / tool_response vary by tool

These are NOT a fixed schema — they mirror each tool's own arguments and result:

// Bash
"tool_input":  { "command": "npm test", "description": "Run tests" }
// Edit
"tool_input":  { "file_path": "/p/app.js", "old_string": "a", "new_string": "b" }
// Read
"tool_input":  { "file_path": "/p/README.md" }
// MCP tools use namespaced names, e.g. tool_name: "mcp__memory__create_entities"

tool_response likewise differs by tool — plain text output, a structured file result, or { "type": "error", "text": "…" } on failure. Store it as opaque JSON keyed by tool_name.

Security notes

  • The key is stored as plaintext in ~/.claude/settings.json and is injected into every subprocess Claude spawns — that is the only way native HTTP hooks can read it. Use a write-only ingest key with a small blast radius.
  • install backs up settings.json to settings.json.bak and merges idempotently. uninstall removes only entries pointing at your ingest URL.

Configuration

The ingest URL defaults to the Radware production endpoint. Override it per-install with --url or the RADWARE_AGENTIC_PROTECTION_INGEST_URL env var (useful for staging or local testing).

Development

node test/mock-ingest.mjs 8099   # stub endpoint that logs events and returns 200 empty
npm test                         # smoke test against a throwaway config dir

End-to-end with real Claude Code: run the mock, then CLAUDE_CONFIG_DIR=/tmp/cc-test node bin/cli.js install --url http://127.0.0.1:8099/ingest --key sk-test, launch CLAUDE_CONFIG_DIR=/tmp/cc-test claude from any project, do something tool-heavy, and watch the events land in the mock.