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

@protohax/userscript

v0.5.0

Published

TypeScript definitions for the ProtoHax userscript API.

Readme

@protohax/userscript

TypeScript definitions for the ProtoHax userscript API.

With a userscript, you write your own ProtoHax client modules in TypeScript. You give each module a name and options, then react to what happens in the game:

  • game events and packets
  • entities and the world
  • the local player and their inventory
  • the raw packet connection

Your modules show up in the client menu under the Script category, next to the built-in ones.

This package only contains types: a single index.d.ts, with no runtime code. The ProtoHax client provides the real implementation when your script runs. Imports like defineModule, moduleManager, the entity classes, nbt and utils resolve to the client's own objects at load time, so your script and the client share the same game state.

Install

The easiest way to start is the template. It comes with a bundler, a deploy script and an example module:

git clone https://github.com/hax0r31337/ProtoHax-UserScript-Template my-script

Or add the types to an existing project:

npm install --save-dev @protohax/userscript

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true
  }
}

skipLibCheck: true is recommended. Versions before 0.5 need it, because their declarations include a harmless duplicate declaration from the client's packet definitions. Your own code is fully type-checked either way.

Building

Your script must build to a single ES module that keeps @protohax/userscript as an external import. That import is how your code connects to the client's objects, so don't bundle it. With rollup, that means:

  • external: ["@protohax/userscript"]
  • output.format: "es"

Put the built file in the client's scripts folder (%APPDATA%\ProtoHax\scripts on Windows).

Quick start

import { defineModule } from "@protohax/userscript";

defineModule(
  { name: "AutoSprint" },
  {
    // Declared once and shared by every session. Each key is both the name on
    // ctx.options and the label shown in the menu.
    speed: { type: "number", def: 1, min: 0, max: 5, step: 0.1 },
    rotate: { type: "boolean", def: true, child: {
      smoothing: { type: "number", def: 10, min: 1, max: 20, step: 1 },
    } },
  },
  // Runs once per session, with typed options on ctx.options.
  (ctx) => {
    // The game's own movement tick, fired natively before the player moves,
    // so position and motion you set here apply to this tick.
    ctx.on("movement_tick", (state) => {
      state.strafe(ctx.options.speed.value, 1);
    });
  },
);

A module has two parts:

  • The schema is read once, when the module is registered. It declares the options, including nested child options, group drawers and modes.
  • The setup function runs once per session with a fresh ctx, and your options arrive typed on ctx.options. Listeners you add through ctx only fire while the module is enabled, and they're removed automatically when the session ends.

When a choice needs its own options or behavior, declare it as a mode. A mode with a setup function gets its own per-session listeners, which only fire while the module is enabled and that mode is selected:

import { defineModule, mode } from "@protohax/userscript";

defineModule(
  { name: "Velocity" },
  {
    mode: { type: "modes", modes: {
      Vanilla: mode((ctx) => {
        ctx.onPacket("set_entity_motion", (packet) => { packet.isCancelled = true; });
      }),
      Reversal: mode({
        ticks: { type: "number", def: 2, min: 0, max: 5, step: 1 },
      }, (ctx) => {
        ctx.on("tick", () => { /* ctx.options.ticks.value */ });
      }),
    } },
  },
  () => { },
);

The older builder form, defineModule(meta, (options) => (ctx) => { ... }), still works for existing scripts.

What you can access

Everything starts from ctx.session (a GameSession):

| Area | Entry point | | --- | --- | | Entities & players | ctx.session.entityState — entities, playerList, localPlayer | | Local player actions | ctx.session.entityState.localPlayer — interactEntity(), interactBlock(), swingItem(), breakingController, rotationScheduler, … | | Movement | ctx.on("movement_tick", …) hands you a MovementState — strafe(), jump(), setMotion(), and SprintIntent / SneakIntent requests (docs) | | Input | ctx.on("input_tick", …) hands you an InputState — override the move keys (MoveKey), stick and sneak (docs) | | World & blocks | ctx.session.levelState — getBlock(), rayTrace(), getChunk(), chunkStorage, … | | Items & inventory | ctx.session.itemState — controller.moveItem(), openContainer, item registry | | Packets | ctx.session.connection — sendOutgoingPacket(), queue checkers, flushHeld() | | Options | ctx.options (typed from your schema), each Option<T> handle, nested trees via option.child / configurable.getOption() | | Other modules | moduleManager — inspect and observe any module | | Audio | registerAudio(bytes) hands you an AudioClip — play(), pause(), stop(), volume, looping, played by the client UI (docs) | | Math / NBT | utils (vectors, AABB, A* pathfinding, hashing, …) and nbt (patched prismarine-nbt) |

Listen to game events with ctx.on(name, cb), and to packets with ctx.onPacket(name, cb) or ctx.onInjectedPacket(name, cb). Every event name, packet name and payload is typed.

Handler parameters are inferred, so you rarely need to write a packet type yourself. When you do (say, for a helper that takes a payload, or a field stored in a variable), all the protodef types are exported under the packets namespace:

import type { packets } from "@protohax/userscript";

function isSprinting(p: packets.packet_player_auth_input): boolean {
  return p.input_data.includes("sprint_down");
}

const origin: packets.vec3f = { x: 0, y: 0, z: 0 };

Documentation

Full guides and API reference: userscript.protohax.net.