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 🙏

© 2025 – Pkg Stats / Ryan Hefner

keyspy

v1.1.1

Published

A cross-platform global keyboard and mouse listener for Node.js

Readme

keyspy 🕵️

A powerful, cross-platform keyboard and mouse event listener for Node.js

keyspy is a modern, lightweight library that provides global keyboard and mouse event monitoring across Windows, macOS, and Linux. Unlike other solutions, keyspy uses pre-compiled native binaries and a multi-process architecture for maximum stability and compatibility.

🤔 Why keyspy?

Choosing the right keyboard listener for your Node.js project can be challenging. Here's how keyspy compares to other popular solutions:

| Feature | Electron globalShortcut | IOHook | keyspy | |---------|-------------------------|---------|------------| | Zero compilation | ❌ Electron required | ❌ node-gyp required | ✅ Pre-compiled binaries | | Package size | 🟡 Large (Electron) | 🟡 Medium | ✅ Small (on-demand download) | | System shortcuts | ✅ Yes | ✅ Yes | ✅ Yes | | Event blocking | ✅ Yes | ✅ Yes | ✅ Yes | | Cross-platform | ✅ Yes | ✅ Yes | ✅ Yes | | TypeScript | ✅ Built-in | 🟡 Community types | ✅ Built-in | | Stability | ✅ Very stable | 🟡 Can crash Node.js | ✅ Multi-process isolation |

🎯 keyspy Advantages

  • 🚀 Zero Setup: Pre-compiled binaries downloaded automatically, no compilation required
  • 📦 Smaller Package: Binaries downloaded on-demand (not bundled), reducing package size by ~90%
  • 📱 Universal macOS: ARM64 + x86_64 universal binaries for all Apple Silicon and Intel Macs
  • 🔄 Modern Stack: TypeScript, automated testing, and modern development tools
  • 🏗️ Automated Releases: GitHub Actions handle cross-platform compilation and publishing
  • 🛡️ Stable Architecture: Multi-process design prevents Node.js crashes
  • ⚡ High Performance: Optimized native implementations for each platform

📚 Project Origin: keyspy is a modernized version of node-global-key-listener, rebuilt with modern tooling and enhanced features.

✨ Features

  • 🌍 Cross-platform: Windows, macOS, and Linux (X11) support
  • 🔒 System-level capture: Intercept any key combination, including OS shortcuts
  • 🎯 Event blocking: Prevent captured events from reaching other applications
  • 📦 TypeScript ready: Full type definitions included
  • 🛡️ Stable architecture: Multi-process design prevents crashes
  • High performance: Optimized native implementations for each platform

🔧 Platform Support

| Platform | Status | Implementation | Tested On | |----------|--------|---------------|-----------| | Windows | ✅ Full | C++ (Low-level hooks) | Windows 10/11 | | macOS | ✅ Full | Swift (CGEventTap) | macOS 10.14+ | | Linux | ✅ X11 Only | C++ (XInput2) | Ubuntu, Arch Linux |

🚀 Quick Start

import { GlobalKeyboardListener } from "keyspy";

const keyspy = new GlobalKeyboardListener();

// Listen to all keyboard events
keyspy.addListener((e, down) => {
    console.log(`${e.name} ${e.state} [${e.rawKey._nameRaw}]`);
});

🔍 Testing Your Setup

# Clone and test
git clone https://github.com/teomyth/keyspy.git
cd keyspy
npm install
npm run cli             # General key monitoring with clean table output

The test tool will show real-time key detection for all keyboard and mouse events.

Features:

  • ✅ Clean, aligned table output (no terminal echo interference)
  • ✅ Real-time event detection with optimized column widths
  • ✅ Standard Unicode symbols for modifier keys (⇧⌃⌥⌘)
  • ✅ Detailed key information (name, code, modifiers, location)
  • ✅ Cross-platform compatibility

Sample Output

When you run npm run cli, you'll see a clean, real-time table like this:

Default output (KEYSPY_DEBUG=0 - clean table format):

=== keyspy Key Monitor ===
Real-time keyboard and mouse event detection
Debug level: 0
Exit: ESC, Ctrl+C, or CMD+Q
==========================================

#     Time       State Key Name           Mods           Raw Key                Loc            vKey
#1    14:32:16   DN    F3                                kVK_F3                 0.0,0.0        0x63
#2    14:32:16   UP    F3                                kVK_F3                 0.0,0.0        0x63
#3    14:32:17   DN    A                                 kVK_ANSI_A             0.0,0.0        0x00
#4    14:32:17   UP    A                                 kVK_ANSI_A             0.0,0.0        0x00
#5    14:32:18   DN    SPACE              ⌘              kVK_Space              0.0,0.0        0x31
#6    14:32:19   DN    MOUSE LEFT                        CGMouseButton.left     245.7,156.3    0x00
#7    14:32:20   DN    C                  ⇧+⌃+⌥+⌘        kVK_ANSI_C             0.0,0.0        0x08
#8    14:32:21   DN    UNKNOWN                           UNKNOWN_0xA0           0.0,0.0        0xA0

Debug output (KEYSPY_DEBUG=1 - with unknown key detection):

#8    14:32:21   DN    UNKNOWN                           UNKNOWN_0xA0           0.0,0.0        0xA0
❓ UNKNOWN key detected - vKey: 0xA0, Raw: UNKNOWN_0xA0
   This key is not in our lookup table but was captured successfully

Some keys may appear as UNKNOWN if they're not in our lookup tables, but you'll still get the key code (vKey) to identify them.

📖 Usage Examples

Basic Event Logging

import { GlobalKeyboardListener } from "keyspy";

const keyspy = new GlobalKeyboardListener();

keyspy.addListener((e, down) => {
    console.log(`Key: ${e.name}, State: ${e.state}, Location: ${e.location}`);
});

Capturing System Shortcuts

// Capture Cmd+Space (macOS) or Win+Space (Windows)
keyspy.addListener((e, down) => {
    if (e.state === "DOWN" && e.name === "SPACE" &&
        (down["LEFT META"] || down["RIGHT META"])) {
        console.log("System shortcut captured!");
        return true; // Prevent the event from reaching other apps
    }
});

// Capture Alt+F4
keyspy.addListener((e, down) => {
    if (e.state === "DOWN" && e.name === "F4" &&
        (down["LEFT ALT"] || down["RIGHT ALT"])) {
        console.log("Alt+F4 intercepted!");
        return true;
    }
});

Advanced Configuration

const keyspy = new GlobalKeyboardListener({
    // Global app name for permission requests (default: "KeySpy")
    appName: "My Awesome App",

    windows: {
        onError: (errorCode) => console.error("Windows error:", errorCode),
        onInfo: (info) => console.info("Windows info:", info)
    },
    mac: {
        onError: (errorCode) => console.error("macOS error:", errorCode),
        // Platform-specific app name (overrides global appName)
        appName: "My Mac App"
    },
    x11: {
        onError: (errorCode) => console.error("Linux error:", errorCode),
        // Platform-specific app name (overrides global appName)
        appName: "My Linux App"
    }
});

Cleanup and Resource Management

// Remove specific listener
const myListener = (e, down) => { /* ... */ };
keyspy.addListener(myListener);
keyspy.removeListener(myListener);

// Clean shutdown
keyspy.kill(); // Removes all listeners and stops the key server

📦 Installation

npm install keyspy
# or
yarn add keyspy

The package automatically detects your platform and downloads the appropriate pre-compiled binary during installation. No compilation required!

🛠️ Development

# Clone and setup
git clone https://github.com/teomyth/keyspy.git
cd keyspy
npm install

# Build native binary for development
npm run build:native

# Development commands
npm run dev          # TypeScript watch mode
npm run build        # Production build
npm test             # Run all tests
npm run cli          # Interactive testing with clean table output

Building Native Binaries

For development, you can rebuild the native binaries:

# Auto-detect your platform
npm run build:native

# Force specific platform
npm run build:native -- --platform darwin  # macOS (requires Xcode)
npm run build:native -- --platform linux   # Linux (requires X11 dev libraries)
npm run build:native -- --platform win32   # Windows (requires MinGW)

# Show detailed build output
npm run build:native -- --verbose

Code Quality

npm run lint         # Check code quality with Biome
npm run lint:fix     # Auto-fix issues
npm run format       # Format code
npm run clean        # Remove build artifacts

# Release management
npm run release:patch    # Bump patch version (1.0.0 → 1.0.1)
npm run release:minor    # Bump minor version (1.0.0 → 1.1.0)
npm run release:major    # Bump major version (1.0.0 → 2.0.0)
npm run release:dry      # Preview release without publishing

Testing

npm run test:unit        # Unit tests
npm run test:integration # Integration tests (may prompt for permissions)
npm run test:integration:safe # Check permissions first, then run integration tests
npm run check-permissions # Check if permissions are granted
npm run dev:cli          # Manual interactive testing with clean table output

# Verbose levels for CLI tool
npm run cli              # Clean table output only (default)
npm run cli:v            # + UNKNOWN key detection (-v)
npm run cli:vv           # + All debug information (-vv)
npm run cli:vvv          # + Detailed debug info (-vvv)

# Or use environment variable
KEYSPY_DEBUG=0 npm run cli         # Clean table output only (default)
KEYSPY_DEBUG=1 npm run cli         # + UNKNOWN key detection
KEYSPY_DEBUG=2 npm run cli         # + All debug information
KEYSPY_DEBUG=3 npm run cli         # + Detailed debug info

# Skip integration tests if permissions are not available
SKIP_INTEGRATION_TESTS=true npm test

📋 API Reference

GlobalKeyboardListener

Constructor

new GlobalKeyboardListener(config?: IConfig)

Methods

  • addListener(listener: IGlobalKeyListener): Promise<void> - Add event listener
  • removeListener(listener: IGlobalKeyListener): void - Remove event listener
  • kill(): void - Stop all listeners and cleanup

Event Object

interface IGlobalKeyEvent {
  name: string;           // Key name (e.g., "A", "SPACE", "F1")
  state: "UP" | "DOWN";   // Key state
  rawKey: IGlobalKey;     // Raw key information
  vKey: number;           // Virtual key code
  scanCode: number;       // Scan code
  location: [number, number]; // Mouse location (for mouse events)
}

Configuration Options

interface IConfig {
  appName?: string;        // Default app name for permission requests (default: "KeySpy")
  disposeDelay?: number;   // Delay before disposing server when no listeners (default: 100ms)

  // Platform-specific configurations
  windows?: {
    onError?: (errorCode: number) => void;
    onInfo?: (info: string) => void;
    serverPath?: string;   // Custom path to Windows binary
  };

  mac?: {
    onError?: (errorCode: number | null) => void;
    onInfo?: (info: string) => void;
    serverPath?: string;   // Custom path to macOS binary
    appName?: string;      // macOS-specific app name (overrides global appName)
  };

  x11?: {
    onError?: (errorCode: number | null) => void;
    onInfo?: (info: string) => void;
    serverPath?: string;   // Custom path to Linux binary
    appName?: string;      // Linux-specific app name (overrides global appName)
  };
}

🔒 Security & Permissions

macOS

  • Requires Accessibility permissions in System Preferences
  • First run will prompt for permission automatically

Windows

  • May require administrator privileges for system-wide hooks
  • Some antivirus software may flag the binary (false positive)

Linux

  • Requires X11 display server
  • May need to run with appropriate user permissions

🔧 Troubleshooting

Common Issues

  1. Binary download fails: Visit GitHub Releases to download manually, or build from source with npm run build:native
  2. Permission denied: Make sure the binary has execute permissions (chmod +x bin/*)
  3. CI environments: Set KEYSPY_SKIP_DOWNLOAD=true to skip binary download

Platform Requirements

  • macOS: macOS 10.14+, supports both Intel and Apple Silicon
  • Linux: X11 display server, x64 architecture
  • Windows: Windows 10+, x64 architecture

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

📄 License

MIT License - see LICENSE file for details.