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

@quenty/studio-bridge

v0.7.0

Published

WebSocket-based bridge for running Luau scripts in Roblox Studio

Readme

@quenty/studio-bridge

WebSocket-based bridge for running Luau scripts in Roblox Studio.

How It Works

┌─────────────┐    WebSocket     ┌──────────────────┐
│   Node.js   │◄───────────────►│  Studio Plugin    │
│   Server    │   ws://localhost │  (auto-injected)  │
│             │                  │                   │
│ 1. Start WS │                  │ 4. Connect + hello│
│ 2. Inject   │                  │ 5. Run script     │
│    plugin   │                  │ 6. Stream output  │
│ 3. Launch   │                  │ 7. scriptComplete │
│    Studio   │                  │                   │
└─────────────┘                  └──────────────────┘
  1. Start WebSocket server on a random port
  2. Build a .rbxm plugin via rojo build --plugin with the port and session ID baked in
  3. Plugin is placed in Studio's plugins folder, launch Studio
  4. Plugin connects, handshakes with session ID
  5. Server sends execute with Luau script, plugin runs it via loadstring() + xpcall()
  6. Plugin streams LogService output back as batched messages
  7. Plugin sends scriptComplete — server can send another execute or shutdown

The session ID (random UUID) prevents stale plugins from previous runs from interfering.

CLI

# Run a script file
studio-bridge run test.lua

# Run inline script
studio-bridge exec 'print("hello world")'

# With a specific place file (builds a minimal place via rojo if omitted)
studio-bridge run test.lua --place build/test.rbxl

# Interactive terminal mode (keeps Studio alive between executions)
studio-bridge terminal

# Terminal mode with initial script
studio-bridge terminal --place build/test.rbxl --script init.lua

# Debug output
studio-bridge run test.lua --verbose

Global Options

| Option | Alias | Default | Description | |--------|-------|---------|-------------| | --place | -p | — | Path to a .rbxl place file (builds minimal place via rojo if omitted) | | --timeout | — | 120000 | Timeout in milliseconds | | --verbose | — | false | Show internal debug output | | --logs / --no-logs | — | true | Show execution logs in spinner mode |

Terminal Mode

Keeps Studio alive and provides an interactive REPL. Type Luau, see results, repeat — no re-launch between executions.

$ studio-bridge terminal --place build/test.rbxl
Studio connected.

────────────────────────────────────────────────────────
❯ print("hello")
────────────────────────────────────────────────────────
  ctrl+enter to run · ctrl+c to clear · .help for commands

| Key | Action | |-----|--------| | Enter | New line | | Ctrl+Enter | Execute buffer | | Ctrl+C | Clear buffer (exit if empty) | | Ctrl+D | Exit |

| Command | Description | |---------|-------------| | .help | Show keybindings and commands | | .exit | Exit terminal mode | | .run <file> | Execute a Luau file | | .clear | Clear the editor buffer |

API

import { StudioBridge } from '@quenty/studio-bridge';

const bridge = new StudioBridge({ placePath: './build/test.rbxl' });
await bridge.startAsync();

const result = await bridge.executeAsync({
  scriptContent: 'print("Hello from studio-bridge!")',
  timeoutMs: 90_000,
  onOutput: (level, body) => console.log(`[${level}] ${body}`),
});

console.log(result.success); // boolean
console.log(result.logs);    // all captured output, newline-separated

// Can call executeAsync() again without relaunching Studio
await bridge.stopAsync();

StudioBridgeServerOptions

| Field | Type | Default | Description | |-------|------|---------|-------------| | placePath | string | — | Path to .rbxl file (auto-builds via rojo if omitted) | | timeoutMs | number | 120_000 | Default timeout for operations | | onPhase | (phase) => void | — | Progress callback: building, launching, connecting, executing, done | | sessionId | string | auto UUID | Session ID for concurrent isolation |

ExecuteOptions

| Field | Type | Default | Description | |-------|------|---------|-------------| | scriptContent | string | required | Luau source code to execute | | timeoutMs | number | inherited | Timeout for this execution | | onOutput | (level, body) => void | — | Called for each log message |

StudioBridgeResult

| Field | Type | Description | |-------|------|-------------| | success | boolean | true if the script ran without errors | | logs | string | All captured output, newline-separated |

WebSocket Protocol

All messages are JSON: { "type": string, "payload": object }.

Plugin to Server:

| Type | Payload | Description | |------|---------|-------------| | hello | { sessionId } | Handshake | | output | { messages: [{ level, body }] } | Batched log output | | scriptComplete | { success, error? } | Script finished |

Output levels: "Print", "Info", "Warning", "Error" (matches Enum.MessageType).

Server to Plugin:

| Type | Payload | Description | |------|---------|-------------| | welcome | { sessionId } | Handshake accepted | | execute | { script } | Luau script to run | | shutdown | {} | Disconnect |

Testing

pnpm test              # Unit tests (no Studio needed)
pnpm test:watch        # Watch mode
pnpm test:integration  # End-to-end (requires Studio)

| Layer | What it tests | Studio? | |-------|--------------|---------| | Unit (pnpm test) | Protocol, template substitution, path resolution, WebSocket lifecycle | No | | Integration (pnpm test:integration) | Full pipeline: rojo build, plugin injection, Studio launch, output capture | Yes |

Platform Support

| Platform | Studio Location | Plugins Folder | |----------|----------------|----------------| | Windows | %LOCALAPPDATA%\Roblox\Versions\*\RobloxStudioBeta.exe | %LOCALAPPDATA%\Roblox\Plugins\ | | macOS | /Applications/RobloxStudio.app/Contents/MacOS/RobloxStudioBeta | ~/Documents/Roblox/Plugins/ |

Future Plans

  • StudioTestService integration — Use ExecuteRunModeAsync() / EndTest() for better isolation vs. loadstring()
  • Structured test results — Protocol extension for typed result messages instead of log parsing