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

@intentic/desktop

v1.226.1

Published

Drive a desktop from Node: capture the screen, move the pointer, click, type and scroll, Windows and Linux, no native modules

Downloads

3,957

Readme

@intentic/desktop

Drive a desktop from Node, on Windows and Linux, with no native modules.

Capture the screen, move the pointer, click, type, press chords, scroll and drag.

import { desktop } from "@intentic/desktop";

const screen = desktop();
const { width, height } = await screen.frame();
const png = await screen.capture();
await screen.click({ x: 840, y: 512 }, "left");
await screen.type("hello world");
await screen.key("ctrl+s");

// …and operating applications, not just pixels
await screen.launch("https://example.com");
const open = await screen.windows();          // app, title, bounds, focused
await screen.focusWindow(open[0]!.id);        // typing goes to the FOCUSED window
await screen.writeClipboard("some text");

What this package is not

It knows nothing about agents, capabilities, permissions or sandboxes. It takes coordinates and text and makes a computer do something; whether that is allowed is a question asked before these methods are ever called. Its one consumer today is @intentic/host, which owns that question: and the split is what makes the policy testable, since a real click can only be verified by a human watching a screen.

How it works, per platform

Windows: PowerShell into user32.dll. SetCursorPos + mouse_event for the pointer, keybd_event for chords, and SendKeys for text only. The split is deliberate: SendKeys is the only one that handles arbitrary unicode sensibly, and the only one that cannot press the Windows key: so text uses it and chords do not. Screen capture is System.Drawing. No install step, nothing left resident.

Linux: two backends behind one interface. X11 lets any client synthesise input, so xdotool does everything with no privileges. Wayland does not, so the pointer goes through ydotool (which needs /dev/uinput) and text/keys prefer wtype (which does not). Missing tools raise a DesktopError carrying the one-line install for the specific thing that is absent.

macOS: capture works, input does not. The methods throw rather than silently doing nothing.

Windows enumeration: EnumWindows supplies every visible top-level window, including several owned by one process; process lookup adds the app name, and the remaining P/Invokes supply bounds and foreground state. Get-Process.MainWindowHandle is intentionally not used because it collapses a workspace and its dialog into one row.

Taking focus on Windows needs more than SetForegroundWindow. Windows refuses that call from a process that is not already in the foreground, and refuses it quietly: it flashes the taskbar button and leaves the keyboard where it was, so the next type or key goes to whatever the person at that desk had open. A backend running from a fresh powershell.exe misses every qualifying condition at once, and a machine whose foreground-lock timeout has been raised (gaming and anti-focus-stealing utilities do this) closes the last of them for good. So focusWindow briefly attaches its input queue to the foreground window's thread and the target's, which is what earns the right, and then checks: it raises a DesktopError rather than returning to a caller that is about to type into the wrong window.

Wayland enumeration mostly cannot happen, and that is a design decision rather than a gap: a compositor does not let one client enumerate another's windows, the same protection that stops it synthesising input. The wlroots family (sway, Hyprland) answers swaymsg -t get_tree to anyone who can reach the socket, so those are supported; everything else gets a sentence explaining why, rather than an empty list that reads as "nothing is open".

Two details worth knowing

Coordinates are screenshot pixels. frame() also reports the virtual desktop's origin, which is not always (0,0): a second monitor to the left of the primary one gives Windows a negative left edge. The backends add it back, so callers work in screenshot pixels throughout and multi-monitor setups stop being a source of silent misclicks.

One key vocabulary, three renderings. keys.ts fixes the names (X11 keysyms: Return, Escape, BackSpace, Page_Up, plus ctrl/alt/shift/super and the aliases people actually type: enter, esc, win, cmd) and each backend translates. Without it every caller would be platform-aware, which is the exact coupling this package exists to remove.

Key files