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

create-agent-hook-ts

v1.1.5

Published

A TypeScript library for creating agent hooks.

Readme

create-agent-hook-ts

Typescript library to streamline the development of VSCode Copilot Agent Hooks. Provides a full suite of dynamic types that cover all parts of hook creation and additional utilities including the tool matching pattern defined in Claude Code that is not implemented in GitHub Copilot Hooks.

This library exposes a createAgentHook method that accepts the hook event being targetted, a callback defining the logic of your hook, and an optional list of tool names to target for [Pre|Post]ToolUse hooks.

Designed to be used in a .ts script that is run as a Copilot Hook command.

.github/hooks/your-hook.json :

{
	"hooks": {
		"PostToolUse": [
			{
				"type": "command",
				"command": "./.github/hooks/your-hook.ts",
			},
		],
	},
}

Key Features

Handles stdio for Hook Input/Output

Don't worry about handling hook stdio and reading from buffers. For hook input, createAgentHook reads from stdin and parses the hook input before sending it to the user defined callback with the correctly inferred types and enriched fields.

For hook output, the current implementation of createAgentHook just outputs the user defined callback return value directly to stdout, following the standard method of sending Copilot Hook output. In the future, there are plans to provide an output utility that will abstract the nuanced decisions that Copilot Hooks make based on output fields and process exit code.

Enables Targetting Specific Tools in ToolUse Hooks

Following the lead of the Claude Hooks matcher for tool names (which is not natively supported by VSCode Copilot Hooks), users can add an optional array of tool names to target for [Pre|Post]ToolUse hooks. The user's hook callback will only fire if the hook input's tool_name matches a tool name in the array. Instead of using a pattern match (like is done in Claude Hooks), we use the full tool name to enable type inference for the tool_input based on targetted tools. This is supplemented by providing intellisense for possible tool names when users are configuring targetToolNames.

Example:

// Will only fire callback on tools that edit files
createAgentHook(
    "PostToolUse",
    (input) => {
        return {};
    },
    ["apply_patch", "create_file", "replace_string_in_file"],
);

Extensive Dynamically Typed Intellisense

All types and literals that you need to know to write a hook from start to finish can be read from intellisense via type inference on createAgentHook parameters. Every step for invoking createAgentHook has dynamic intellisense based on previous parameters that constrain types to only possible values.


Hook Event Names - Displays all available hook events when first invoking createAgentHook


Input Fields - Dynamically infers hook specific input type from hookEventName

ex. PostToolUse vs SessionStart


Output Fields - Dynamically infers output type when defining return object


Tool Names - Displays all default tool names for [Pre|Post]ToolUse Hooks when configuring targetToolNames


Tool Input - Infers input.tool_input as the union of targetted tools

(ex. query is the only shared input field among [fetch_webpage, file_search])


Provides a type-guard isToolInputOf to type narrow tool_input to access properly typed tool inputs for Copilot's default OpenAI/Claude Tools


JSDoc Type descriptions that pull from the VSCode Copilot Agent Hooks Documentation

Enriches tool_input and normalizes OpenAI and Claude Tool Inputs

(i.e. ApplyPatch vs ReplaceStringInFile)

Claude ReplaceStringInFile tool's structure ({ oldString, newString, filePath }) was added to the OpenAI ApplyPatch tool with values parsed from the ApplyPatch input string. This is automatically added to enrich the tool_input when createAgentHook detects tool_name ApplyPatch from hook input, allowing users to access ApplyPatch update values and file name without parsing the string themselves.

Examples

Typecheck and Lint Hook on PostToolUse for tools that edit code

#!/usr/bin/env bunx

import fs from "fs";
import util from "node:util";
import child_process from "node:child_process";
import { createAgentHook } from "create-agent-hook-ts";
const exec = util.promisify(child_process.exec);

createAgentHook(
	"PostToolUse",
	async (input) => {
		let didLintFail = false;
		let didTscFail = false;
		const lintPromise = exec(
			"npx nx test:lint one-explorer-tools-package-bump",
		).catch((error) => {
			didLintFail = true;
			return { stdout: error.stdout, stderr: error.stderr };
		});
		const tscPromise = exec(
			"npx nx test:typecheck one-explorer-tools-package-bump",
		).catch((error) => {
			didTscFail = true;
			return { stdout: error.stdout, stderr: error.stderr };
		});
		const [lintResult, tscResult] = await Promise.all([
			lintPromise,
			tscPromise,
		]);
		if (didLintFail)
			fs.writeFile("lint.log", lintResult.stdout, "utf-8", () => {});
		else fs.rm("lint.log", { force: true }, () => {});
		if (didTscFail)
			fs.writeFile("typecheck.log", tscResult.stdout, "utf-8", () => {});
		else fs.rm("typecheck.log", { force: true }, () => {});
		if (didLintFail || didTscFail) {
			const message = `Your last edit failed ${
				didLintFail ? `linting (check ${input.cwd}/lint.log)` : ""
			}${
				didLintFail && didTscFail ? " and " : ""
			}${didTscFail ? `type check (check ${input.cwd}/typecheck.log)` : ""}. Please fix the errors and try again.`;
			return {
				decision: "block",
				reason: "Failed lint or type check",
				hookSpecificOutput: {
					hookEventName: "PostToolUse",
					additionalContext: message,
				},
			};
		}
		return {
			hookSpecificOutput: {
				hookEventName: "PostToolUse",
				additionalContext: "Lint and TSC checks passed, good job!",
			},
		};
	},
	[
		"apply_patch",
		"create_file",
		"replace_string_in_file",
		"multi_replace_string_in_file",
	],
);