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

kustom-mc

v1.0.3

Published

CLI and type library for kustompack development

Readme

Kustom MC

CLI and type library for developing kustompack scripts with TypeScript support.

Installation

npm install kustom-mc
# or
npx kustom-mc <command>

Quick Start

# Create a new project
npx kustom-mc init my-pack

# Build scripts
npx kustom-mc build

# Build and watch for changes
npx kustom-mc build --watch

# Push to development server
npx kustom-mc push

Table of Contents


Script Format

Scripts use the defineScript helper for type safety:

import { defineScript, Props } from "kustom-mc";

export default defineScript({
  props: {
    player: Props.String(),
    message: Props.String("Hello!"),
    count: Props.Number(1),
  },

  run() {
    const player = this.getPlayer(this.props.player);
    player.sendMessage(this.props.message);
    
    this.exit("done");
  },
});

Script Context

The run function uses this to access the script context:

| Property / Method | Description | |----------|-------------| | this.props | Validated props passed to the script | | this.executor | Who executed the script (player or console) | | this.methods | Methods defined in defineScript({ methods }) | | this.exit(value) | Exit with return value | | this.emit(event, ...data) | Emit event to parent | | this.getPlayer(name) | Get player by name | | this.on(event, fn) | Register event listener | | this.once(event, fn) | Register one-time listener | | this.off(event, fn?) | Remove event listener(s) | | this.open(script, props?) | Open a sub-process | | this.call(method, ...args) | Call a named method | | this.putData(key, value) | Store data on process | | this.getData(key) | Get stored data | | this.getId() | Get process ID |

Standalone classes available as globals:

new Camera(x, y, z)       // Cinematic camera
new ChatGUI()             // Chat-based UI
new ChatSession(id, cb)   // Chat session
new Storage(namespace?)    // Persistent storage
new Screen(size, options?) // Inventory GUI

Props System

Define typed properties for your scripts:

import { defineScript, Props } from "kustom-mc";

export default defineScript({
  props: {
    // Required string (no default)
    player: Props.String(),
    
    // String with default
    title: Props.String("Welcome"),
    
    // Number with default
    amount: Props.Number(100),
    
    // Boolean with default
    enabled: Props.Boolean(true),
    
    // Object/Array
    data: Props.Object({}),
    items: Props.Array([]),
    
    // Union type (one of these values)
    mode: Props.Union("easy", "normal", "hard"),
  },

  run() {
    console.log(this.props.player);  // string
    console.log(this.props.amount);  // number
    console.log(this.props.enabled); // boolean
  },
});

Typed Events System

Scripts can declare typed events for type-safe communication between parent and child scripts:

// digicode.ts
import { defineScript, Props } from "kustom-mc";

export default defineScript({
  props: {
    player: Props.String(),
    code: Props.String("1234"),
  },

  // Declare events with typed payloads
  events: {
    onSuccess: Props.Boolean(),  // Event with boolean payload
    onUnlock: Props.String(),    // Event with string payload
  },

  run() {
    // Emit typed events - TypeScript checks payload types!
    this.emit("onSuccess", true);      // OK
    this.emit("onUnlock", "1234");     // OK
    // this.emit("onSuccess", "wrong"); // TS Error: expected boolean
    // this.emit("unknown", true);      // TS Error: unknown event
  },
});
// main.ts
import { defineScript, Props } from "kustom-mc";
import digicode from "./digicode";

export default defineScript({
  props: {
    player: Props.String(),
  },

  run() {
    const child = digicode.run({
      player: this.props.player,
      code: "1234",
    });

    // TypeScript infers callback parameter types from digicode's events!
    child.on("onSuccess", (value) => {
      // value is typed as boolean
      console.log("Code accepted!", value);
    });

    child.on("onUnlock", (code) => {
      // code is typed as string
      console.log("Entered code:", code);
    });

    // Use then() for exit handling (not events)
    child.then((result) => {
      console.log("Child exited with:", result);
    });
  },
});

Event Methods

Scripts without events declaration still work - events are untyped (backward compatible).

const child = otherScript.run({ ...props });

// Listen for events
child.on("eventName", (value) => {});    // Listen for event
child.once("eventName", (value) => {});  // Listen once, auto-remove after first call
child.off("eventName", callback);         // Remove specific listener
child.off("eventName");                   // Remove all listeners for event

Multiple Listeners

Multiple listeners can be registered for the same event:

child.on("onSuccess", () => console.log("First handler"));
child.on("onSuccess", () => console.log("Second handler"));
// Both handlers are called when onSuccess is emitted

Importing Scripts

Scripts can import and run other scripts:

ScriptProcess API

When running an imported script, you get a ScriptProcess:

const child = otherScript.run({ ...props });

child.on("eventName", (value) => {});    // Listen for events (typed if declared)
child.once("eventName", (value) => {});  // Listen once (auto-remove)
child.off("eventName", callback);         // Remove specific listener
child.off("eventName");                   // Remove all listeners for event
child.emit("eventName", ...args);         // Send event to child
child.call("methodName", ...args);        // Call method on child
child.then((result) => {});               // Handle completion
child.catch((error) => {});               // Handle errors
child.getId();                            // Get process ID
child.hasExited();                        // Check if completed

Screen GUI System

Create inventory-based GUIs:

import { defineScript, Screen, Props } from "kustom-mc";

export default defineScript({
  props: { player: Props.String() },

  run() {
    const player = this.getPlayer(this.props.player);

    // Create a 27-slot screen (3 rows)
    const screen = new Screen(27, {
      usePlayerInventorySlots: true,  // Use player inv for GUI components
      persistenceKey: "my-gui",       // Enable persistence
    });

    // Or with custom texture
    const texturedScreen = new Screen("my_gui", 54);

    // Add text overlay
    const title = screen.appendText("My GUI", 12, -30, true);

    // Add buttons
    screen.addButton(13, "confirm_button", "Click me!", (event) => {
      console.log("Button clicked!");
      event.cancel();  // Prevent item pickup
    });

    // Handle events
    screen.on("open", (p) => console.log(p.getName() + " opened"));
    screen.on("close", (p) => {
      this.exit(null);
    });

    // Open for player
    screen.open(player);
  },
});

Screen Options

new Screen(size, {
  persistenceKey: string,           // Enable persistence with this key
  usePlayerInventorySlots: boolean, // Use player inv for GUI components
  id: string,                       // Unique identifier for persistence path
  persist: boolean,                 // Enable content slot persistence
  persistScroll: boolean,           // Enable scroll position persistence
});

Player Inventory Slots

Use player inventory slots for extended GUIs (like numpad):

const screen = new Screen(9, {
  usePlayerInventorySlots: true,  // Enable player inv slots
});

// Chest slots: 0-8 (for 9-slot screen)
// Player inventory slots: 54-89
// Mapping: main inv (9-35) -> 54-80, hotbar (0-8) -> 81-89

screen.addButton(54, "button_1", null, () => addDigit(1));
screen.addButton(81, "confirm", null, () => confirm());

Containers & Layouts

Organize elements in containers with grid or flex layouts:

// Create a scrollable container
const container = screen.createContainer({
  startSlot: 10,
  rows: 3,
  cols: 7,
  scrollable: true,
  maxCapacity: 100,
});

// Add items dynamically
for (let i = 0; i < 50; i++) {
  container.pushButton("item_" + i, "Item " + i, (e) => {
    console.log("Clicked item", i);
  });
}

// Scroll controls
screen.addButton(8, "arrow_right", "Next", () => {
  container.scrollPage(1);
  screen.refresh();
});

screen.addButton(0, "arrow_left", "Previous", () => {
  container.scrollPage(-1);
  screen.refresh();
});

// Check pagination state
container.hasNextPage();      // boolean
container.hasPreviousPage();  // boolean
container.getCurrentPage();   // 0-based page number
container.getPageCount();     // total pages

Container Methods

// Adding elements
container.pushButton(texture, title, onClick);  // Next available slot
container.putButton(index, texture, title, onClick);  // Specific index
container.pushContentSlot();  // Slot for player items
container.pushCover();        // Visual cover element
container.pushContainer(config);  // Nested container

// Layout configuration (chainable)
container
  .setLayout("flex")     // "grid" or "flex"
  .setDirection("row")   // "row" or "column"
  .setGap(1)             // Gap between items
  .setRows(3)
  .setCols(7);

// Scrolling
container.scroll(offset);      // Relative scroll
container.scrollTo(index);     // Scroll to specific item
container.scrollToPage(page);  // Go to page

// Events
container.on("scroll", (offset, page) => {});
container.on("childAdded", (child, index) => {});
container.on("capacityReached", () => {});

Elements

Create different types of GUI elements:

import { Element } from "kustom-mc";

// Button element
const button = Element.button("my_texture", "Button Title");
button.onClick((e) => console.log("Clicked!"));
button.onRightClick((e) => console.log("Right clicked!"));
button.onShiftClick((e) => console.log("Shift clicked!"));

// Display-only element (no interaction)
const display = Element.display("info_icon");

// Content slot (accepts player items)
const slot = Element.contentSlot();
slot.on("afterItemPlace", (item, player, index) => {
  console.log(player.getName() + " placed " + item.getMaterialName());
});
slot.on("afterItemTake", (player, index) => {
  console.log(player.getName() + " took item from slot " + index);
});

// Cover element (visual overlay)
const cover = Element.cover();
cover.setColor("#FF0000");  // Red cover

Element Chainable Methods

element
  .setTexture("new_texture")
  .setTitle("New Title")
  .setLore(["Line 1", "Line 2"])
  .setHideTooltip(true)
  .setInteractive(false)
  .setData({ custom: "data" });

Chat GUI System

Create chat-based interactive UIs:

run() {
  const gui = new ChatGUI();
  
  gui.addLine((line) => {
    line.center()
        .background("#1a1a1a")
        .addBoldText("Welcome!", "gold");
  });
  
  gui.addEmptyLine();
  
  gui.addLine((line) => {
    line.spaceBetween()
        .addButton("[Accept]", "/accept", "Click to accept", "green")
        .addButton("[Decline]", "/decline", "Click to decline", "red");
  });
  
  gui.setBlocking(true);  // Capture chat input
  
  gui.onInput((playerName, message, guiId) => {
    console.log(playerName + " typed: " + message);
  });
  
  gui.open(this.props.player);
}

ChatGUILine Methods

line
  .padding(10)                    // Add padding
  .center()                       // Center content
  .spaceBetween()                 // Space between items
  .background("#1a1a1a")          // Background color
  .addText("Hello", "white")      // Add text
  .addBoldText("Bold!", "gold")   // Bold text
  .addButton("[Click]", "/cmd", "Hover text", "green");

Camera System

Create cinematic camera views:

run() {
  const player = this.getPlayer(this.props.player);
  const loc = player.getLocation();
  
  // Create camera at player's position
  const cam = new Camera(loc.getX(), loc.getY() + 5, loc.getZ());
  
  // Start spectating
  cam.startCameraMode(this.props.player);
  
  // Camera controls
  cam.lookAt(0, 64, 0);           // Look at coordinates
  cam.lookAtPlayer("OtherPlayer"); // Look at player
  cam.setRotation(90, -30);        // Set yaw/pitch
  cam.moveTo(100, 70, 100);        // Teleport camera
  
  // Transparent view (see through blocks)
  cam.setTransparentView(true);
  
  // Animations
  const anim = new Animation();
  
  anim.createAnimation()
    .start(0, 64, 0)
    .end(100, 64, 100)
    .duration(5)
    .camera(cam.getId())
    .onEnd(() => console.log("Animation done!"))
    .build()
    .start();
  
  // Stop spectating
  cam.stopSpectating();
  cam.remove();
}

Custom Blocks (Shaper)

Define custom blocks with models and collision:

run() {
  // Custom blocks are defined declaratively using defineBlock():
  // See "Block Definition File" below for the recommended approach.
  
  // Spawn instances via the block's auto-generated placement item,
  // or programmatically from a defineBlock's event handlers.
}

Block Definition File

// blocks/tower.ts
import { defineBlock } from "kustom-mc";

export default defineBlock({
  id: "tower",
  model: "kustom:block/tower",
  collision: { width: 1, height: 3, depth: 1, cornerY: 0 },
  autoPlace: true,  // Auto-generate placement item
  
  onClick(event) {
    event.player.sendMessage("Tower clicked!");
  },
  
  onBreak(event) {
    return false;  // Prevent breaking
  },
});

Custom Items

Define custom items with events:

// items/magic_wand.ts
import { defineItem } from "kustom-mc";

export default defineItem({
  id: "magic_wand",
  material: "STICK",
  model: "kustom:item/magic_wand",
  displayName: "<gold>Magic Wand",
  description: "A powerful magical artifact",
  
  onRightClick(event) {
    event.player.sendMessage("<rainbow>Woosh!</rainbow>");
  },
  
  onLeftClick(event) {
    // Attack with wand
  },
  
  onDamageEntity(event) {
    // Custom damage logic
  },
});

BetterModel Entities

Spawn and control animated entities:

run() {
  // BetterModel entities are managed through the Java API.
  // Entity spawning and control is handled server-side.
  // Scripts can interact with entities through event callbacks
  // in defineBlock/defineItem definitions.
}

Commands

CLI Commands

kustom init [project-name]

Create a new kustompack project.

npx kustom-mc init my-pack
npx kustom-mc init           # Initialize in current directory
npx kustom-mc init --force   # Overwrite existing files

kustom build

Compile TypeScript to JavaScript.

npx kustom-mc build              # Build all scripts
npx kustom-mc build --watch      # Watch mode
npx kustom-mc build --no-validate # Skip validation
npx kustom-mc build --clean      # Clean output before building

kustom bundle

Create a distributable zip file.

npx kustom-mc bundle                    # Create kustompack.zip
npx kustom-mc bundle -o my-pack.zip     # Custom output name
npx kustom-mc bundle --include-source   # Include .ts files

kustom validate

Check scripts for errors.

npx kustom-mc validate           # Validate all scripts
npx kustom-mc validate --strict  # Fail on warnings

kustom new <type> <name>

Generate a new script from template.

npx kustom-mc new script my-script    # General script
npx kustom-mc new block my-block      # Block definition
npx kustom-mc new item my-item        # Item definition
npx kustom-mc new gui my-gui          # GUI script

In-Game Commands

Define commands declaratively using defineCommand:

import { defineCommand } from "kustom-mc/command";

export default defineCommand({
  id: "mycommand",
  description: "A custom command",
  execute(ctx) {
    if (ctx.player) {
      ctx.player.sendMessage("Hello from mycommand!");
    }
    
    // Need full API (camera, shaper, items, etc.)? Run a script:
    // ctx.run("my-script", { target: ctx.args[0] });
  },
  tabComplete(ctx) {
    return ["option1", "option2"];
  }
});

Configuration

kustom.config.json

{
  "include": [
    "scripts/**/*.ts",
    "blocks/**/*.ts",
    "items/**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ],
  "outDir": "dist",
  "lib": ["scripts/lib"],
  "manifest": {
    "id": "my-pack",
    "name": "My Pack",
    "version": "1.0.0",
    "scope": "global"
  },
  "server": {
    "url": "http://localhost:8765"
  },
  "bundle": {
    "output": "dist/kustompack.zip",
    "include": ["**/*.js", "textures/**/*", "gui/**/*", "models/**/*"]
  }
}

Configuration Options

| Option | Description | |--------|-------------| | include | Glob patterns for script files | | exclude | Patterns to exclude | | outDir | Output directory for compiled scripts | | lib | Directories containing shared libraries | | manifest.id | Unique pack identifier (required) | | manifest.name | Human-readable pack name | | manifest.version | Pack version (semver) | | manifest.scope | Pack scope: "global", "world", or "player" | | server.url | Server URL for kustom push deployment | | bundle.output | Output path for bundle zip | | bundle.include | Files to include in bundle |


Project Structure

my-kustom-pack/
├── kustom.config.json
├── tsconfig.json
├── package.json
├── scripts/
│   ├── lib/           # Shared utilities
│   │   └── utils.ts
│   ├── main.ts        # Main scripts
│   └── digicode.ts
├── blocks/
│   └── tower.ts       # Block definitions
├── items/
│   └── magic_wand.ts  # Item definitions
├── gui/
│   └── buttons/       # GUI textures
├── textures/
│   └── item/          # Item textures
├── models/
│   └── block/         # Block models
└── dist/              # Compiled output
    └── scripts/

License

MIT