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

@annetutil/petrusshka

v0.12.1

Published

Petrusshka network CLI emulator for browsers and workers

Readme

@annetutil/petrusshka

Network CLI devices running in Go WebAssembly. Works in a browser or module worker.

import { loadPetrusshka } from "@annetutil/petrusshka";
const runtime = await loadPetrusshka();
const lab = runtime.createLab([
  { id: "edge", vendor: "huawei", profile: "huawei-vrp",
    initial: { hostname: "edge", interfaces: [{ name: "GigabitEthernet0/0/1" }] } },
]);
const session = lab.open("edge");
const completion = lab.assist(session.sessionId, "dis", "tab");
const result = lab.execute(session.sessionId, "display current-configuration");
const snapshot = lab.snapshot();
lab.close(session.sessionId);
lab.dispose();

lab.assist(sessionId, line, key) completes or describes an unfinished command without executing it, adding it to history, or changing device state. key is "tab", "help", or "f1"; the returned value is {handled, line, output}. Vendor policy decides which help key is handled: Huawei, Cisco, Arista, and Junos use "help", RouterOS uses "f1", and PC does not handle a help key. Tab behavior also follows the vendor: cycling for Huawei, unique completion for Cisco and Arista, a completion list for Junos, and common-prefix completion for RouterOS and PC.

Assistance is limited to one line of at most 64 KiB without NUL, CR, or LF. Suggestions come from the implemented command tree, schema, and current model; they do not promise byte-exact vendor help. Incomplete quotes are not completed. PC path completion reads only the virtual filesystem and does not perform shell expansion.

Pass snapshot as the second argument to createLab to restore device state. Sessions are not restored. A command returning question accepts its answer through another execute call on the same session. failed does not roll back previous commands. exitCode contains the exact status when the session supplies one. Unknown API operations and invalid sessions throw errors.

xterm terminal adapter

The browser-only @annetutil/petrusshka/terminal entrypoint connects an already opened xterm terminal to execute and assist. It accepts synchronous or asynchronous callbacks and uses the standard CommandResult and Assistance shapes from the main SDK entrypoint.

Available since @annetutil/petrusshka 0.11.0; requires xterm.js 6.

import {Terminal} from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import type {Assistance, CommandResult, OpenResult} from "@annetutil/petrusshka";
import {attachTerminal} from "@annetutil/petrusshka/terminal";

interface Transport {
  open(deviceId: string): Promise<OpenResult>;
  execute(sessionId: string, line: string): Promise<CommandResult>;
  assist(
    sessionId: string,
    line: string,
    key: "tab" | "help" | "f1",
  ): Promise<Assistance>;
  close(sessionId: string): Promise<void>;
}

declare const transport: Transport; // RPC client for a module worker
const session = await transport.open("edge");

const terminal = new Terminal();
terminal.open(document.querySelector<HTMLElement>("#terminal")!);

const binding = attachTerminal(terminal, {
  prompt: session.prompt,
  history: [], // Optional saved command history (since 0.11.1)
  execute: (line) => transport.execute(session.sessionId, line),
  assist: (line, key) => transport.assist(session.sessionId, line, key),
});

The binding owns terminal input and output until binding.dispose(). The application still owns the xterm instance, transport, and CLI session. Before a reset or reconnect, stop input, wait for the current request, and detach the binding:

binding.setEnabled(false);
await binding.whenIdle();
binding.dispose();
await transport.close(session.sessionId);
terminal.dispose();

setEnabled(false) lets in-flight work finish, preserves queued actions, and ignores new input. setEnabled(true) resumes only this manual pause. If execute or assist throws or rejects, binding.faulted becomes true, the adapter displays the error, preserves later queued actions, and ignores new input until binding.recover(). Recovery never retries the failed request or changes the enabled state. After failed execute, it keeps the command in the transcript but clears the editable draft before continuing the queue; after failed assist, it preserves the draft.

clear() works while disabled, faulted, or closed. It clears the screen and scrollback while keeping the prompt, current line, and history; a closed session does not print another prompt. whenIdle() resolves when the current drain stops, including when disabled or faulted. Supported editing keys are Enter, Backspace, Up/Down, Tab, ?, F1, Ctrl+C, Ctrl+V followed by one printable character, and Cmd+K. Other cursor editing keys are not implemented.

onResult, onError, and onBusyChange may be async. The adapter does not wait for them. Their errors are reported through globalThis.reportError or console.error and do not stop the queue; an onError failure does not invoke onError again.

Do not call terminal.write() while attached: redraws assume the binding is the only writer. External writes are safe after binding.dispose().

The main @annetutil/petrusshka entrypoint does not import DOM, xterm, or the terminal adapter and continues to work in a module worker. The terminal entrypoint imports the official @xterm/addon-serialize runtime dependency to preserve colored output when an edited prompt has moved into scrollback. @xterm/xterm remains an optional peer used by the adapter's types; install it only when using the adapter.

NETCONF

Profiles can expose NETCONF through the same device state as their CLI. lab.netconfCapabilities(deviceId) returns the profile capabilities, or an empty array when NETCONF is unavailable. lab.netconf(deviceId, xml) accepts a complete unframed rpc element and returns the complete rpc-reply.

Each netconf call creates a short-lived session inside WebAssembly; it does not open SSH. Invalid XML and API usage throw an error. A valid request rejected by NETCONF returns rpc-error in the XML response. Long-lived sessions, lock, and the NETCONF candidate datastore are not supported.

Build with make wasm:build; package with make wasm:pack from the repository root. Go 1.26.0 or newer is required. The Go compiler and bundled wasm_exec.js must come from the same toolchain.

The build derives the exported Vendor and Profile unions from the Go registry and writes src/vendor-types.ts. Do not edit the generated file. Consumers continue to import both types from @annetutil/petrusshka.

Devices with a filesystem

const lab = runtime.createLab([
  { id: "pc", vendor: "pc", initial: { hostname: "pc-lab" } },
]);
lab.writeFile("pc", "/etc/demo.conf", "enabled=yes\n");
const session = lab.open("pc");
const result = lab.execute(session.sessionId, `
for file in /etc/*.conf; do
  cat "$file"
done
`);
console.log(result.output);
console.log(result.exitCode); // 0
console.log(lab.readFile("pc", "/etc/demo.conf"));

The file API reads and replaces UTF-8 text at absolute virtual paths. Parent folders must exist. Models that implement the filesystem capability share the same data between shell sessions, file API calls and snapshots. The shell in this example accepts multiple lines; an incomplete construct returns question: "> " and continues through the next execute call. Devices without the filesystem capability reject file API calls. Shell commands never run host programs.

Релиз

Версия @annetutil/petrusshka совпадает с версией проекта: тег v1.2.3 публикует npm-пакет 1.2.3. Источник версии — git-тег; 0.0.0 в package.json и lockfile служит заглушкой для локальной сборки.

Из чистой ветки main выполни make release:tag в корне репозитория. По умолчанию повышается patch; для других уровней — make release:tag BUMP=minor или make release:tag BUMP=major. Команда пушит main и новый тег в origin. Без предыдущих тегов первый patch-релиз — v0.0.1.

Workflow npm-publish.yml запускает проверки, собирает WASM, подставляет версию из тега, публикует npm-пакет и затем создаёт GitHub Release. Для публикации используй secret репозитория NPM_TOKEN с правом публикации @annetutil/petrusshka и включённым Bypass 2FA. Пакет публикуется без provenance, поскольку исходный репозиторий приватный. PR и ручной запуск без параметра tag выполняют только проверки и сборку.

Если публикация не состоялась и нужно использовать исправленный workflow, запусти его из main с существующим тегом:

gh workflow run npm-publish.yml --ref main -f tag=v1.2.3

Workflow соберёт исходники указанного тега и опубликует ту же версию. Если npm-пакет уже опубликован, а создание GitHub Release завершилось ошибкой, восстанови только GitHub Release, без повторной публикации npm.

Для выпуска через агента используй локальный skill $release [patch|minor|major].