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

@hasna/snapshots

v0.2.1

Published

Runtime snapshot and restore layer for Hasna local open-source developer environments.

Readme

@hasna/snapshots

Runtime snapshot and restore layer for the local Hasna open-source developer environment.

@hasna/snapshots captures a point-in-time view of local runtime resources, stores it in SQLite, and can build guarded restore plans for the parts that are safe to recreate automatically.

What It Captures

  • machine identity and runtime context
  • tmux sessions, windows, panes, and pane working directories
  • per-pane resume identity: the newest opencode2 session (session_v2 in ~/.local/share/opencode/opencode.db) and the newest Claude Code JSONL (~/.claude/projects/<slug>/) whose recorded working directory matches the pane — so a tmux layout destroyed by an outage can be rebuilt by resuming the agent sessions that were live in each pane
  • Hasna project registry output when projects list --json is available
  • local process summaries with secret-like arguments redacted
  • session, browser, and desktop state when companion Hasna CLIs or state folders exist
  • diagnostics for optional integrations that are absent or unavailable

Missing optional tools do not fail a snapshot. They become diagnostic resources so the snapshot still explains what was visible at capture time.

Safety Model

Restore is dry-run by default.

The built-in restore allowlist is intentionally narrow:

  • project resources can plan creation of missing project directories.
  • tmux-session, tmux-window, and tmux-pane resources can plan detached tmux layout recreation.
  • other resource kinds are observed and skipped unless future adapters explicitly support them.

Execution requires both --apply and --yes. Restore never kills existing sessions or overwrites existing service files. Existing project directories and tmux sessions become noop operations, and child tmux resources are blocked by default when the target session already exists. Use --merge-existing only when you explicitly want to mutate a live tmux session.

tmux restore is best effort. The default mode is layout-only: it recreates sessions, windows, panes, working directories, layouts, and active selection where safe, but it does not replay captured commands. Use --tmux-mode resume-marked to replay only commands that were explicitly marked restartable.

Install

bun install
bun run build

The package stores data in snapshots.sqlite by default. The data home is resolved through the @hasna/paths resolver — the XDG data home ~/.local/share/hasna/snapshots once adopted, otherwise the legacy ~/.hasna/snapshots (an existing local store never becomes invisible on upgrade). The exact-app HASNA_SNAPSHOTS_DIR override and the data-kind HASNA_DATA_HOME override are honored by the resolver. Override the store path with:

HASNA_SNAPSHOTS_DIR=/path/to/data
HASNA_SNAPSHOTS_DB_PATH=/path/to/snapshots.sqlite

Resume-identity sources and the restore freshness gate can also be configured:

HASNA_SNAPSHOTS_OPENCODE_DB=/path/to/opencode.db          # default ~/.local/share/opencode/opencode.db
HASNA_SNAPSHOTS_CLAUDE_PROJECTS_DIR=/path/to/projects    # default ~/.claude/projects
HASNA_SNAPSHOTS_MAX_AGE=72h                              # default restore max-age (duration string)
HASNA_SNAPSHOTS_CAPTURE_LEASE_TTL_MS=240000              # capture lease TTL (default 240s)
HASNA_SNAPSHOTS_CAPTURE_LEASE_WAIT_MS=30000              # max wait for a live capture lease (default 30s)

Captures against one store are serialized by a short-lived SQLite lease (capture_leases table, same watchdog pattern as the fleet's cron guards): a concurrent capture (e.g. the */5 cron firing while a manual capture is in flight) waits for the holder, and a crashed holder's lease self-expires after the TTL. If the lease cannot be acquired within the wait window the capture still proceeds — saveSnapshot is idempotent, so an overlapping duplicate snapshot becomes a no-op (recorded as a capture.lease-unavailable audit event) instead of a failed transaction.

SDK

The package exposes typed SDK exports from the root package and focused subpaths:

import { SnapshotStore, captureAll, createRestorePlan } from "@hasna/snapshots";
import { planService } from "@hasna/snapshots/service";

Capture and store a snapshot:

import { SnapshotStore, captureAll } from "@hasna/snapshots";

const store = new SnapshotStore({ path: "/tmp/snapshots.sqlite" });
try {
  const capture = await captureAll({
    include: ["machine", "projects", "tmux"],
    tmuxPaneTailLines: 0,
  });

  const snapshot = store.saveSnapshot(capture.resources, {
    name: "before-upgrade",
    diagnostics: capture.diagnostics,
    sourceStatuses: capture.sourceStatuses,
  });

  console.log(snapshot.id, snapshot.resourceCount);
} finally {
  store.close();
}

Build a guarded restore plan without applying it:

import { SnapshotStore, createRestorePlan } from "@hasna/snapshots";

const store = new SnapshotStore();
try {
  const [snapshot] = store.listSnapshots(1);
  if (!snapshot) throw new Error("No snapshots found");

  const resources = store.getSnapshotResources(snapshot.id);
  const policies = store.listPolicies();
  const plan = createRestorePlan(snapshot, resources, policies, {
    include: ["tmux-session:work"],
    dependencyMode: "parents",
    targetMode: "strict",
  });

  store.saveRestorePlan(plan);
  console.log(plan.id, plan.summary, plan.autopilot);
} finally {
  store.close();
}

Service planning is SDK-only until callers explicitly write/apply the generated plan:

import { planService } from "@hasna/snapshots/service";

const plan = planService({ intervalSeconds: 300 });
console.log(plan.kind, plan.path, plan.note);

Interface Parity

| Workflow | CLI | SDK | MCP | HTTP server | | --- | --- | --- | --- | --- | | Capture snapshot | snapshots capture, snapshots daemon once | captureAll, SnapshotStore.saveSnapshot | capture_snapshot | POST /snapshots | | List snapshots | snapshots list | SnapshotStore.listSnapshots | list_snapshots | GET /snapshots | | List capture runs | snapshots runs | SnapshotStore.listCaptureRuns | not exposed | not exposed | | Capture freshness | snapshots freshness | freshness | not exposed | not exposed | | Read snapshot/resources | snapshots show, snapshots resources | SnapshotStore.getSnapshot, SnapshotStore.getSnapshotResources, SnapshotStore.listResources | get_snapshot | GET /snapshots/:id | | Plan restore | snapshots plan, snapshots restore without --apply | createRestorePlan, SnapshotStore.saveRestorePlan | plan_restore | not exposed | | Apply restore | snapshots restore --apply --yes | createRestorePlan(..., { apply: true, yes: true }) | intentionally not exposed | not exposed | | Policy management | snapshots policy list/set | SnapshotStore.listPolicies, SnapshotStore.upsertPolicy | intentionally not exposed | not exposed | | Service planning | snapshots service plan/status/install | planService, serviceStatus, applyServicePlan | intentionally not exposed | not exposed |

MCP intentionally exposes only capture, list, read, and plan tools. Restore execution and service installation mutate local machine state, so they stay in the CLI/SDK surfaces where callers must opt in explicitly.

CLI

snapshots capture --name before-upgrade
snapshots list
snapshots runs --limit 5
snapshots freshness --threshold 900
snapshots show <snapshot-id>
snapshots resources --limit 50
snapshots resources <snapshot-id> --tree
snapshots plan <snapshot-id> --resource kind:tmux-session --with-dependencies
snapshots restore <snapshot-id> --resource tmux-session:work --with-dependencies --apply --yes
snapshots restore --plan <plan-id> --plan-hash <hash> --apply --yes
snapshots restore <snapshot-id> --max-age 72h
snapshots policy list
snapshots policy set kind:process ignore --reason "processes are observe-only"
snapshots daemon once --tmux-tail-lines 0
snapshots daemon run --interval 300 --tmux-tail-lines 20
snapshots service plan
snapshots service install --apply --yes
snapshots service status

All commands emit JSON so agents can consume stable contracts.

Snapshot summaries include per-source status, duration, resource count, diagnostic count, and a degraded flag when a source returns warnings or errors. Daemon captures can use --tmux-tail-lines 0 to skip pane scrollback tails for faster topology snapshots.

Restore plans include an autopilot assessment. By default, only low-risk project directory creation can be marked safe for autopilot. tmux mutations require approval, and shell command replay is forbidden for autopilot.

Restore carries a freshness gate: --max-age <duration> (or HASNA_SNAPSHOTS_MAX_AGE) refuses snapshots older than the configured limit with a logged, audit-trailed error instead of planning or applying a stale restore. Durations accept ms, s, m, h, d, and compounds like 1h30m; a bare number means seconds. The gate is recorded on the plan and re-checked when a saved plan is applied.

MCP

snapshots-mcp provides a minimal stdio JSON-RPC bridge with these tools:

  • capture_snapshot
  • list_snapshots
  • get_snapshot
  • plan_restore

Local HTTP Server

snapshots-serve
curl http://localhost:7337/health
curl http://localhost:7337/snapshots
curl -X POST http://localhost:7337/snapshots -H 'content-type: application/json' -d '{"name":"manual"}'

Release Verification

See the hardening roadmap ledger guide for the 17-item coordination, evidence, blocker, and closure requirements.

bun run typecheck
bun test
bun run build
bun run verify:release

Publishing still depends on normal external credentials for npm and GitHub.