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

@type-x/runtime

v0.4.7

Published

Run commands with the same runtime context as @type-x/cli

Downloads

1,659

Readme

@type-x/runtime

@type-x/runtime lets you build a normal npm CLI while still getting the same runtime context used by x.

Main API

Use initCli in your entrypoint:

import { initCli, type CommandContext } from "@type-x/runtime";

type Store = {
  runs: number;
};

async function main(context: CommandContext<Store>): Promise<void> {
  const runs = (await context.store.get("runs")) ?? 0;
  await context.store.set("runs", runs + 1);

  const gitStatus = await context.exec("git status --short", {
    throwOnError: false,
    silent: true,
  });

  context.log.info(`command: ${context.command.name}`);
  context.log.info(`runs: ${runs + 1}`);
  context.log.info(`git status exit code: ${gitStatus.exitCode}`);
}

initCli(main);

Context

The injected context exposes:

  • command
  • request
  • store
  • log
  • ui
  • exec
  • git
  • io
  • env

request.flags contains parsed CLI flags. A flag used once is exposed as a single string or boolean; a repeated flag is exposed as an array:

my-cli --param a --param b --verbose
context.request.flags.param; // ["a", "b"]
context.request.flags.verbose; // true

This is the default runtime.repeatedFlags: "array" behavior. To keep only the last value for repeated flags, configure the runtime:

initCli(main, {
  runtime: {
    repeatedFlags: "last",
  },
});

Exec

context.exec() runs a command and returns:

  • exitCode
  • stdout
  • stderr

Pass a string to run through the shell:

await context.exec("git status --short");

Pass an argv tuple to run without shell interpolation:

await context.exec(["git", "checkout", branchName]);

Options include:

  • mode "capture" is the default and buffers stdout/stderr while optionally streaming them. "inherit" attaches the child process directly to the current terminal so interactive commands like sudo, editors, or prompts behave normally. In this mode, stdout and stderr are returned as empty strings.
  • silent When false, stream command output to the current process stdout/stderr. This is the default.
  • throwOnError When false, return non-zero exit codes instead of throwing. By default it is true.
  • timeoutMs Stop the command after this many milliseconds. The process receives SIGTERM first and escalates to SIGKILL if it does not exit. Timed-out commands use exit code 124.

When throwOnError is left on and the command exits non-zero, exec() throws a CommandExecError from @type-x/runtime. The error message is a stable summary, and the raw process output is available on the error instance through stdout, stderr, exitCode, command, cwd, and mode. @type-x/runtime also exports isCommandExecError(error) for structural narrowing when you do not want to rely on instanceof.

Example for an interactive command:

await context.exec("sudo npm install -g some-tool", {
  mode: "inherit",
});

Example for handling command failures:

import { CommandExecError, initCli, isCommandExecError } from "@type-x/runtime";

try {
  await context.exec("git push");
} catch (error) {
  if (error instanceof CommandExecError) {
    console.error(error.exitCode);
    console.error(error.stderr);
  }

  if (isCommandExecError(error)) {
    console.error(error.command);
    console.error(error.stderr);
  }

  throw error;
}

Store

context.store persists JSON state for the command package. Keys can be top-level or dot paths:

await context.store.set("providers.github", "hello");
await context.store.get("providers.github"); // "hello"

This writes:

{
  "providers": {
    "github": "hello"
  }
}

Failures

Use context.fail() for expected user-facing failures:

context.fail("GitHub token is required. Run: x auth github");

This prints the message to stderr and sets exit code 1. You can choose a different code:

context.fail("Invalid config", {
  exitCode: 2,
});

Unexpected bugs can still be thrown as normal errors; initCli catches them and sets exit code 1.

Default Store Location

If you do not override runtime.homeDir, the runtime uses:

  • ~/.type-x/<sanitized-package-name>

You can override that explicitly:

initCli(main, {
  runtime: {
    homeDir: "/some/custom/path",
  },
});

Author

Iñigo Taibo