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

@particle-academy/fancy-term-host

v0.2.0

Published

Headless Node terminal backend for @particle-academy/fancy-term — owns the PTYs (node-pty) and the T1/T2/T3 persistence engine (snapshot+replay, retained PTYs, detached pty-host) behind four injected ports. OS-agnostic, zero hard third-party deps.

Readme

@particle-academy/fancy-term-host

The headless Node terminal backend for @particle-academy/fancy-term.

fancy-term is the browser-side React <Terminal> — it renders xterm.js and deliberately never spawns a shell. fancy-term-host is the other half: the Node process that owns the PTYs (via node-pty) and a T1/T2/T3 persistence engine — snapshot+replay, retained PTYs, and a detached pty-host — behind four small injected ports. It runs anywhere Node runs (Electron main, a Laravel queue worker, a plain server): OS-agnostic by construction, with zero hard third-party dependencies (node-pty is a peer the consumer builds).

It can't live inside fancy-term or the other UI packages — node-pty is a native addon that would break their browser builds — so it's an independent sibling you install alongside.

Install

npm install @particle-academy/fancy-term-host node-pty

node-pty is a peer dependency: you own its native build (and, under Electron, the asar-unpack so its .node binary loads outside the archive).

Wire it up

You provide four ports (see docs/ports.md); the core does the rest. The minimal in-process (Tier 1/2) setup:

import {
  configureInProcessBackend,
  inProcessBackend,
  createSnapshotStore,
  type SettingsProvider,
  type Encryptor,
} from "@particle-academy/fancy-term-host";

// 1. Settings (gates the cwd hook + T3). Defaults are sensible — track_cwd on.
const settings: SettingsProvider = { get: (k) => undefined };

// 2. A cipher for at-rest snapshots. A passthrough is fine to start with.
const encryptor: Encryptor = {
  isAvailable: () => false, // → snapshots stored as plaintext gzip
  encrypt: (b) => b,
  decrypt: (b) => b,
};

// 3. Snapshot store (T1) rooted under a writable dir (`<dir>/sessions/...`).
const snapshots = createSnapshotStore({ baseDir: "/var/app/userData", encryptor });

// 4. Configure + grab the backend.
configureInProcessBackend({ settings, snapshots });
const backend = inProcessBackend();

// Spawn a shell and stream it to a fancy-term <Terminal> on the client.
const { id, scrollback } = backend.create({ id: "t1", cols: 80, rows: 24 });
backend.on("data", (tid, data) => sendToClient(tid, data)); // → <Terminal output>
backend.on("exit", (tid) => closeOnClient(tid));

// Client keystrokes (fancy-term `onData`) come back here:
onClientData("t1", (d) => backend.write("t1", d));
onClientResize("t1", (cols, rows) => backend.resize("t1", cols, rows));

The backend's create / write / resize / kill / list and its data / exit / cwd events are the whole surface the wire needs. Pair it with fancy-term's controlled output buffer and onData and you have a live terminal that an agent can also inhabit (via the registerTerminalBridge in @particle-academy/agent-integrations).

Persistence tiers

Switchable behind one PtyBackend interface (see docs/persistence.md):

  • T1 — snapshot & replay. Session state is serialized, (optionally) encrypted, gzipped, and written to <baseDir>/sessions/<id>.snap. A cold start replays it so a reopened terminal shows where it was.
  • T2 — retained PTYs. A PTY flagged setRetained(true) survives a window detach (the live shell keeps running; scrollback replays on reattach) instead of being killed.
  • T3 — detached host. PTYs live in a separate headless pty-host process that survives a full quit of the app. The backend proxies calls over a named pipe (Windows) / unix socket (POSIX); reopening reattaches to the still-running shells.
  • T3+ — per-user OS service. Run the pty-host as a launchd / systemd --user / Windows-task service on its own standalone Node runtime, so an Electron auto-update never pins the consumer binary — terminals survive quits and updates. Subpath @particle-academy/fancy-term-host/service; ensureHostService(...) installs-if-missing → starts, with graceful fallback. See docs/service.md.

Spawning the detached host (T3)

The bundled host script is resolvable without knowing the dist layout:

import { spawn } from "node:child_process";
import { ptyHostScriptPath } from "@particle-academy/fancy-term-host";

const child = spawn(process.execPath, [ptyHostScriptPath(), userDataDir], {
  detached: true,
  stdio: "ignore",
});
child.unref();

Wrap that in a HostSpawner and pass it to configureHostLifecycle(...) to let the core connect-or-spawn-or-fall-back automatically. (require.resolve( "@particle-academy/fancy-term-host/pty-host") also resolves the script.)

cwd tracking (OSC-7)

The host learns each terminal's working directory from OSC-7 escape sequences the shell emits on every prompt, so a resumed shell can start where the old one left off. fancy-term-host injects the prompt hook for you, gated by the track_cwd setting (default on):

| Shell | Mechanism | Status | |---|---|---| | bash | prepends an OSC-7 printf to PROMPT_COMMAND (env) | ✅ | | zsh | generated ZDOTDIR whose rc sources yours + adds a precmd | ✅ | | fish | generated vendor_conf.d via XDG_DATA_DIRS (--on-event fish_prompt) | ✅ | | PowerShell | dot-sourced profile shim wrapping your prompt (appended launch args) | ✅ | | cmd.exe | PROMPT with the $E escape | ⚠️ best-effort (only where the console honors VT in the prompt) |

Every hook overlays your shell config — it never clobbers your prompt or rc. Any shell that can't be hooked degrades silently to the static cwd.

License

MIT


⭐ Star Fancy UI

If this package is useful to you, a quick ⭐ on the repo really helps us build a better kit. Thank you!