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

sysopkit

v0.5.2

Published

SysopKit - System Operations Toolkit

Readme

SysopKit

A TypeScript infrastructure automation toolkit.

  • Agentless — runs simple shell commands on target hosts
  • Zero external NPM dependencies
  • Idempotent operations with change detection
  • Typed configs with structured data instead of string templates
  • Native OpenSSH client with ControlMaster multiplexing
  • Parallel multi-host execution
  • Middlewares: privilege escalation (sudo), tracing, …

Table of Contents

Installation

# pnpm
pnpm add sysopkit
# Bun
bun add sysopkit

Quick Start

import { apply, task } from 'sysopkit';
import { start } from 'sysopkit/start';
import { resolveInventory } from 'sysopkit/inventory';
import { sudo } from 'sysopkit/middleware/sudo';
import { createFile } from 'sysopkit/op/file';
import { sh } from 'sysopkit/op/sh';

const INVENTORY = {
  groups: {
    web: {
      hosts: {
        'web-1': { host: '192.168.1.10', user: 'admin' },
        'web-2': { host: '192.168.1.11', user: 'admin' },
      },
    },
    db: {
      hosts: {
        'db-1': { host: '192.168.1.20' },
      },
    },
  },
};

await start(async () => {
  await using hosts = resolveInventory(INVENTORY);

  await apply('setup nginx', hosts.getByGroup('web'), async () => {
    await sudo(async () => {
      await sh('apt install -y nginx');
      await createFile({
        path: '/etc/nginx/sites-available/default',
        content: 'server { listen 80; }',
        mode: 0o644,
        user: 'nginx',
        group: 'nginx',
      });
      await sh('systemctl enable --now nginx');
    });
  });
});

Packages

| Package | Description | | ---------------------- | --------------------------------------------------- | | sysopkit | Core: context, connectors, middleware, basic ops, … | | @sysopkit/linux | Linux ops: apt, dnf, rpm, systemd, … | | @sysopkit/cli | password prompts, selection menus, confirm dialogs | | @sysopkit/test-utils | Test Utils for ops |

Operations

Operations are the building blocks for infrastructure automation.

Core Operations sysopkit/op/*

| Category | Functions | | ---------- | ----------------------------------------------------------- | | exec | spawn, exec | | sh | sh | | bash | bash, waitPort | | file | createFile, deleteFile, readFile, writeFile, … | | users | createUser, deleteUser, createGroup, deleteGroup, … | | rsync | rsyncPush, rsyncPull | | curl | curl | | ini | serializeIni | | mount | mount, parseFstab, serializeFstab | | proc | waitProcess | | net | parseHosts, serializeHosts | | netcat | waitPort | | ssh | serializeSshConf |

Linux Operations @sysopkit/linux/*

| Category | Functions | | ----------- | -------------------------------------------------------- | | pkg/apt | installPackages, removePackages, … | | pkg/dnf | installPackages, removePackages, … | | pkg/rpm | importKey, | | cpu | lscpu | | disk | lsblk | | kernel | dmesg, lsmod, modinfo, kexecLoad, kexecExec, … | | limits | parseLimitsConf, serializeLimitsConf | | mem | getMemInfo | | os | getOSInfo | | sudoers | serializeSudoersConf | | sysctl | parseSysctlConf, serializeSysctlConf | | systemd | service, daemonReload, setHostname, … |

Connectors

SSH

Uses native OpenSSH with ControlMaster multiplexing for connection reuse.

import { SSHConnector } from 'sysopkit/connector/ssh';
import { start } from 'sysopkit/start';

await start(async () => {
  await using c = new SSHConnector({ host: '192.168.1.1', user: 'sysop' });

  await apply('example', c, async () => {
    await sh('echo hello');
  });
});

Local

Runs commands directly on the local host.

import { LocalConnector } from 'sysopkit/connector/local';
import { start } from 'sysopkit/start';

await start(async () => {
  await using c = new LocalConnector();

  await apply('example', c, async () => {
    await sh('echo hello');
  });
});

Podman

Runs commands inside podman containers via podman exec.

import { PodmanConnector } from 'sysopkit/connector/podman';
import { start } from 'sysopkit/start';

await start(async () => {
  await using c = new PodmanConnector({ container: 'my-app' });

  await apply('example', c, async () => {
    await sh('echo hello');
  });
});

Middleware

Middleware wraps connectors using the decorator pattern.

SudoMiddleware

Prepends sudo to commands.

import { sudo } from 'sysopkit';

await sudo(() => sh('apt update'));

TraceMiddleware

Pipes stdout/stderr through TransformStream and reports output via the reporter.

import { trace } from 'sysopkit';

await trace(() => sh('apt install -y nginx'));

ExpectMiddleware

Watches stderr for a pattern and writes a response to stdin. Useful for interactive prompts.

import { expectStderrPrompt } from 'sysopkit';

await expectStderrPrompt(() => sh('ssh-keygen -f /root/.ssh/id_rsa'), {
  pattern: /passphrase/,
  response: '\n',
});

Inventory

Define hosts and groups with variables. resolveInventory() resolves hosts, merges variables, and returns a ResolvedInventory that creates connections lazily:

const INVENTORY = {
  vars: { env: 'production' },
  groups: {
    web: {
      vars: { role: 'webserver' },
      tags: ['frontend'],
      hosts: {
        'web-1': { vars: { id: 1 } },
        'web-2': { vars: { id: 2 } },
      },
    },
    db: {
      vars: { role: 'database' },
      hosts: {
        'db-1': {},
      },
    },
  },
};

await start(async () => {
  await using hosts = resolveInventory(INVENTORY);

  // Access resolved connectors
  hosts.getByGroup('web'); // connectors for web-1, web-2
  hosts.getByTag('frontend'); // connectors with frontend tag
  hosts.getByName('web-1'); // web-1 connector
  hosts.match('web-*'); // connectors matching glob pattern
  hosts.getAll(); // all connectors
});

Host prefixes determine connection type:

  • ssh: for SSH (default)
  • pod: for Podman containers.

Custom connector factories can be passed via the connectors option.

Variables inherit hierarchically: inventory → group → host.

Apply

apply() orchestrates operations across hosts. Supports single-connector and multi-connector modes:

// Single connector
await apply('name', connector, async () => {
  await sh('hostname');
});

// Multiple connectors with parallel batches
await apply(
  'name',
  hosts.getAll(),
  async () => {
    await sh('hostname');
  },
  { maxFailPercent: 20 },
);

Multi-connector mode processes hosts in parallel batches. The maxFailPercent option aborts remaining batches if the failure threshold is exceeded. On failure, throws ApplyError with all per-host results.

Events

SysopKit uses a type-safe event system with branded symbols. Create custom events and register handlers with on():

import { on, emit, Event, task } from 'sysopkit';

const MY_EVENT: Event<string> = Symbol('my.event');

await task('demo', async (ctx) => {
  ctx.on(MY_EVENT, (data) => {
    console.log('received:', data);
  });

  await task('subtask', async () => {
    emit(MY_EVENT, 'hello');
  });
});

Events propagate up the context parent chain, calling registered handlers at each level.

Change Tracking

Use onChange() to detect scoped changes:

import { onChange, latch } from 'sysopkit';

const restart = latch();
await onChange(restart, async () => {
  await createFile({ path: '/etc/foo.conf', content: 'bar' });
});

if (restart()) {
  // restart foo service
}

Utilities

retry()

Configurable retry with fixed/exponential backoff. Skips on AbortError, optional retryOn predicate.

import { retry } from 'sysopkit';

await retry(() => sh('curl -s http://api/health'), { attempts: 3, delay: 1000 });

timeout()

Wraps a function with an AbortController-based timer. Throws TimeoutError on expiry.

import { timeout } from 'sysopkit';

await timeout(() => sh('long-running-command'), { ms: 30_000 });

sleep()

Abort-aware delay. Rejects with signal.reason on cancellation.

import { sleep } from 'sysopkit';

await sleep(5000); // wait 5 seconds

Dry Run

Set SYSOPKIT_DRY_RUN=1 environment variable or pass dryRun: true:

await start(
  async (ctx) => {
    // Idempotent operations will emit change events, but won't make any actual changes
    await createFile({ path, content });

    if (!ctx.dryRun) {
      await writeFile('path', 'content');
      await sh('script...');
    }
  },
  { dryRun: true },
);

Verbosity

Set SYSOPKIT_VERBOSITY to minimal, normal, debug, or trace.

Error Handling

SysopKit provides typed error classes:

| Error | Description | | ---------------- | ---------------------------------------------------- | | OperationError | Operation-specific failures with context | | ConnectorError | Connection/transport failures | | AbortError | Execution was cancelled via AbortSignal | | TimeoutError | Operation exceeded time limit | | ApplyError | Multi-host apply failures (extends AggregateError) |

Use isAbortError() to check for cancellation:

import { isAbortError } from 'sysopkit';

try {
  await sh('some-command');
} catch (err) {
  if (isAbortError(err)) return; // gracefully handle cancellation
  throw err;
}

SysopKit vs Ansible

SysopKit takes a fundamentally different approach than Ansible. Instead of a YAML-based DSL with its own control flow constructs, SysopKit leverages TypeScript as a full programming language. This eliminates the need for many Ansible-specific features.

Loops and conditionals are handled by standard TypeScript constructs. Where Ansible requires loop: directives and when: conditions, SysopKit uses for loops, map(), filter(), and if/else statements. This means you have the full expressiveness of a programming language rather than being limited to what the YAML DSL supports.

Variable assignment replaces Ansible's register directive. The result of any operation is simply returned as a value that you can store in a variable and use later. There's no special syntax—just normal TypeScript variables.

Typed configs replace string templates with structured data. Instead of generating config files through string interpolation, operations accept typed objects that are serialized correctly. For example, parseResolvConf() and serializeResolvConf() work with typed ResolvConf objects, parseIni() and serializeIni() handle IniDocument structures, and parseSshdConfig() returns typed configuration entries. This eliminates template syntax errors, provides IDE autocomplete for config keys, and catches type mismatches at compile time rather than at deployment.

Templates use TypeScript template literals or any templating library you prefer. Instead of Jinja2 templates in separate files, you can embed values directly in strings with ${variable} syntax, or use libraries like Mustache, Handlebars, or EJS for more complex templating needs.

Error handling uses standard try/catch/finally blocks instead of Ansible's block/rescue/always construct. This gives you more fine-grained control over error handling and cleanup logic.

Dynamic inventory is just code. Instead of configuring plugins in YAML, you can fetch data from any API, parse JSON or YAML, transform it with TypeScript, and build your inventory programmatically. This works with any cloud provider, database, or custom source.

Roles and reuse work through npm packages and ES modules. Instead of Ansible's role directory structure, you can publish reusable automation as npm packages and import them with standard import statements. Version management comes from npm, and you can use any package in the TypeScript ecosystem.

LSP and IDE support leverages the full TypeScript tooling ecosystem. You get inline documentation from JSDoc comments, type-aware autocompletion, and refactoring tools out of the box. No need to install separate language servers or extensions — your editor already understands the code.

License

Licensed under either of