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

@phnx-labs/sessions-cli

v0.8.3

Published

High-performance session index: search, list, and preview agent transcripts

Readme

sessions

High-performance session index for agent transcripts. The installed binary is sessions.

It reads the existing SQLite index at ~/.agents/.history/sessions/sessions.db (override with $SESSIONS_DB). It reads the index directly — no daemon boot, no transcript-directory walk.

npm i -g @phnx-labs/sessions-cli

sessions                                  # browse (TTY): this dir, last 30 days, preview pane, hotkeys
sessions --all                            # every directory, all time
sessions "auth middleware" --json --limit 5
sessions a1b2c3d4 --json
sessions --limit 20 --no-interactive      # print the list instead of the browser

Command reference

scripts/generate-cli-docs.sh                              # refresh docs/
scripts/generate-cli-docs.sh --check                      # fail on stale docs
scripts/generate-cli-docs.sh --out-dir .agents/scratch/cli-docs  # preview

Run after bun install --frozen-lockfile. The script works from any directory; --out-dir is relative to your current directory. Open command-reference.html from the output folder to browse the tree and search commands. CI uploads a sessions-cli-docs artifact on pull requests and pushes to main or release/**. It contains the HTML reference plus Markdown and JSON indexes. Tests and releases check the committed files; commit regenerated docs/ with command changes. Release checks also run with --skip-tests / --skip-build.

Browse the generated HTML command reference or Markdown command index. Regenerate after changing command help with scripts/generate-cli-docs.sh; the test script checks that these files stay current. Generation requires Bun 1.4 or later because the program imports node:sqlite.

Interactive browser

In a terminal, a bare sessions opens the session browser agents sessions had: one filter state shown in the header (device:all · agent:all · team:all · this repo · window:30d), rows with id, agent, version, machine (when the list spans more than one), project, title, ticket/PR, worktree and age, and a preview pane with the highlighted session's card (agent and model, cwd, branch, timing, ticket, then Asked / Doing / Made / Health / Cost / Latest / Details).

| Key | Does | |---|---| | s | search the loaded rows: the visible fields, plus the full transcript text through the index | | a / d / t | cycle agent / machine / team over what the list contains | | p | this directory's subtree ⇄ every directory | | w | window: all → 1d → 7d → 30d | | c | include team-spawned sessions (teammates, headless title generators, Codex sub-agent threads) | | b / * | only bookmarked sessions / bookmark the highlighted one | | tab | hide or show the preview | | y | copy the sessions … command that reproduces the view (printed when no clipboard tool) | | ⏎ | resume: runs agents run <agent> --resume <id> (agents-cli owns resume), plus --device <host> for a --host row, or prints that command when agents is not installed | | esc | leave search (the filter stays), clear the filter, then quit |

A positional query opens the browser on the sessions whose text matches, all time. --all, --teams, --in-team <name>, --bookmarks, --machine <id>, --since, --agent, --path, --skill, --plugin and --routine seed the same state. The browser is skipped — and the plain list printed instead — with --json, --no-interactive, --tree/--flat, a direct id lookup, or whenever stdout/stdin is not a TTY, so machine callers are unaffected. The browser and the printed list hide team-origin sessions unless --teams: Claude SDK spawns the index flags, and Codex sub-agent threads (their transcript's session_meta says source: subagent). --json, and the read a --host caller runs on its peer, return every row, with isTeamOrigin set on those sessions. A row's title skips context a SessionStart hook or the harness injected (## Team & Agents, Codex's /root role) for the first genuine user turn. Ids that share their first 8 characters (Codex's time-ordered ids) are widened until each listed row reads distinctly; --json keeps the index's shortId.

Flags agents sessions had that belong elsewhere now (--active and its status filters, --roots, --print-cmd, --cloud, --browser, --computer) exit 2 and name where that lives instead of being read as search text.

Filter and sort the listing (composable, and honored with or without a query):

sessions --agent claude              # by harness (or the shorthand: --claude)
sessions --agent [email protected]      # harness pinned to a version
sessions --project my-app            # by project NAME (-p)
sessions .                           # by PATH: sessions started in this dir (or under it)
sessions ~/src/app --since 7d        # a bare dir is a path filter; composes with others
sessions --path /abs/dir             # explicit form (alias: --cwd)
sessions --since 7d --until 1d       # active window: 2h/7d/4w/1mo/1y or an ISO date
sessions --sort cost                 # order by cost (also: duration, recent)
sessions --tree                      # group the printed list by project (--flat is the default)
sessions --teams                     # include team-spawned sessions (hidden from the list by default)
sessions --in-team auth-team         # one team: the session that spawned it and its teammates
sessions --bookmarks                 # only bookmarked sessions
sessions --machine yosemite-m2       # only sessions the index recorded on that machine
sessions --skill design              # sessions that invoked a skill: design, or any <plugin>:design
sessions --plugin rush               # sessions that used any skill or command the rush plugin owns
sessions --routines                  # transcripts routine runs archived (alias: --routine)
sessions --routine nightly-review    # one routine's: exact, unique substring, or one-typo name
sessions 2026-10-06T12-00-00-000Z    # every transcript one routine run archived (a run id or its prefix)

--routines / --routine [name] read the transcripts agents-cli archived from routine runs (origin routine), across every directory and all time; every other filter still narrows them. Without --limit they return up to 5000 rows (an ordinary read keeps 20, at most 500), and an explicit --limit is honoured, also in the browser, whose routine pool otherwise holds 1000. A name resolves against the routines with an indexed transcript (exact, then one containing match, then one unambiguous typo); an unknown or ambiguous name exits 1 and lists them. With no name a terminal asks which routine, then browses its transcripts; piped or --json, every routine transcript is returned. Rows carry routineName and routineRunId. A run id names every transcript the run archived, so a one-session verb (render, trace, timeline, preview, artifacts, --resolve) refuses a run with several and lists their session ids; any id prefix several sessions share is refused the same way. Those verbs take the listing's filters to pick one (sessions trace <run-id> --agent [email protected], --project, --path, --since/--until, --routine), applied before the ambiguity check and never capped by --limit; a full session id ignores them except --routine/--skill/--plugin. sessions render <run-id> without --json renders every transcript of the run into one document, and sessions export <run-id> bundles them; the same filters narrow the run first and -n caps it. Routines that never wrote an agent transcript (command-only, deleted jobs) are not in the index: their run history is agents routines runs <name>.

--skill and --plugin read the per-session usage agents-cli indexes (session_resource_usage), case-insensitively. Both compose with every other filter and a full-text query, apply before --limit, ride to --host peers, and narrow <id> and --resolve to that session only when it used them. An index without the usage table is refused rather than read as empty.

Bookmark from a script with sessions bookmark. It writes the same ~/.agents/.history/bookmarks.json the browser's * does, so each sees the other's marks:

sessions bookmark a1b2c3d4 0f9e8d7c   # set (not toggle); --remove clears
sessions bookmark --json              # no ids (or --list): {"bookmarks": [sorted full ids]}
sessions bookmark a1b2 nope --json    # {"results": [{query, id, bookmark} | {query, error}]}, exit 1

A selector is a full id, short id, or unique prefix in the index. An ambiguous one is an error, never the first match. A complete id the index has not seen (a UUID, session_<UUID>, ses_<26 chars>) is kept as given. In a batch the valid ids are applied even when another fails; any failure exits 1 after the whole JSON is written. Bookmarks are per-machine: --host is refused, and sessions --host <h> --bookmarks filters the peer's rows by this machine's bookmarks.

--path/--cwd (and a bare positional with a path sigil — ., .., ~, ~/src/app, ./x, ../x, or an absolute /path) match sessions whose cwd is that directory or under it. --project is the registered project name; --path is where the session actually ran. A plain word (sessions auth, even sessions src where src/ exists) stays an FTS query — only a sigil, or the explicit --path/--cwd flag, means a directory. The path is normalized to an absolute path once, so it composes with every filter, feeds the picker in a TTY, and rides to every --host.

Read other machines' indexes over SSH with --host (short -H; -D/--device is an alias). A target is an OpenSSH alias, user@host, or ssh://user@host; any other scheme is refused. Repeat the flag or comma-separate to read several:

sessions "auth middleware" --host yosemite-s0 --json   # one host
sessions --limit 20 -H muqsit@box                      # a bare listing, remotely
sessions --host yosemite-s0,yosemite-s1                # merge two hosts
sessions -H yosemite-s0 -H box --json --limit 50       # same, repeated

With one host, sessions SSHes there, runs the remote sessions with --local, and streams its stdout back (so --json is byte-for-byte); it exits with the remote's code. One connection per host, no multiplexing.

The one-session reads follow the same host: preview, render (and sessions <id> --markdown), artifacts, trace and timeline with one --host run on that host and print its answer, never this machine's copy of the same id. render -o <file> --host <h> writes the peer's document to the local file. An unreachable host fails the read (ssh's exit 255); several hosts, or a --host with no target in it (--host ,), are refused. --local keeps the read on this machine even with --host. share and tail work on this machine only and refuse --host: pull the session first with sessions import --from-host <h> <id>, or follow it there with ssh <h> sessions tail <id>.

sessions render a1b2c3d4 --host yosemite-s1            # the transcript on yosemite-s1
sessions artifacts a1b2c3d4 report.md -H yosemite-s1   # a file that session wrote there

With several hosts, each peer is read with --json --local (so no peer recurses back over SSH), its rows are tagged with the host they came from, and the results are merged newest-first and capped to --limit. An unreachable or failing peer is reported as a dim warning rather than aborting the whole read. In a TTY the merged rows feed the same picker; with --json/--no-interactive they print as one list.

There is no all/fleet keyword: the read engine keeps no device registry, so name the hosts explicitly. To fan across the whole fleet, feed it from agents-cli:

sessions --host "$(agents devices list --json | jq -r '.[]|select(.tailscale.online).name' | paste -sd,)"

Hidden fast paths (no commander tree):

sessions __query --json --limit 5 -- auth
sessions __serve    # request JSON on fd 3, response on fd 4

Read one session's content:

sessions render a1b2c3d4 -o s.md         # a redacted document: preview card + conversation (owner-only file)
sessions render a1b2c3d4 d4c3b2a1        # several sessions, one document; --reasoning omit|fold|include
sessions a1b2c3d4 --markdown             # just the conversation as Markdown
sessions render a1b2c3d4 --json          # { session, events } JSON
sessions render a1b2c3d4 --include user,assistant   # by role; also --exclude, --first N, --last N
sessions share a1b2c3d4                  # publish a redacted page through `artifacts share`
sessions --resolve a1b2c3d4 --json       # one session's safe metadata (id, unique prefix, label, or keywords)
sessions tail                            # stream the latest Claude/Codex session as it is written
sessions trace a1b2c3d4                  # compact tool-call trajectory
sessions timeline a1b2c3d4               # narration-anchored step list
sessions artifacts a1b2c3d4              # files the session wrote (--json for a list)
sessions artifacts a1b2c3d4 plan.md      # print one written file (by path, basename, or suffix)
sessions preview a1b2c3d4                # the picker's preview card, non-interactive

render output is redacted (secrets + home paths) unless --no-redact (local only).

Search tool calls across sessions (the index's tool tables; read-only):

sessions --include tools --query 'program:git input:merge' --since 7d
sessions --include tools --query 'program:git input:merge' --query 'program:gh output:CONFLICT' --json
sessions --include tools --query program:gh --count --json      # every indexed gh call site

Move sessions between machines as a portable bundle (the agents-cli format):

sessions export --since 7d -o week.bundle
sessions export --since 7d --stdout | ssh boxB 'sessions import -'
sessions import --from-host boxA --since 1d

Or back them up off-box and restore them on any machine:

sessions export --since 30d --to-r2      # managed Phoenix store when signed in ('agents auth login')
sessions import --from-r2                # on a fresh box: recovers your per-account key
sessions export --since 30d --to-r2 --byo   # your own bucket from the r2.backups secrets bundle

Managed backups are always sealed with AES-256-GCM under a per-account key that is escrowed with the store, so a fresh machine can restore. --byo (or AGENTS_SESSIONS_BACKEND=byo) uses the r2.backups bundle, resolved by the installed secrets CLI; add R2_SYNC_ENC_KEY to it so neither Phoenix nor the storage provider can read the objects. To back up to a staging or self-hosted managed store (a backup Worker deployed with agents sessions backup-setup --domain <host>), set AGENTS_SESSIONS_URL=https://<host>; your Phoenix sign-in is sent to it, so use only a store you run or trust.

sessions reads and moves sessions; it does not resume, inject, or stop them — the browser's Enter hands resume to agents run --resume.

Library

The transcript reader is also importable in-process, so you can parse and render a transcript without shelling out to the bin:

import { parseSession, renderConversationMarkdown, buildTrajectory }
  from '@phnx-labs/sessions-cli/reader';

const events = parseSession('/path/to/transcript.jsonl');
const markdown = renderConversationMarkdown(events, meta);

The reader subpath exposes the parse → render → timeline → trajectory → state pipeline (parsers for every harness, the SessionEvent/SessionMeta types, and the render / analysis helpers) with TypeScript declarations. It is a complete superset of the reader agents-cli carries — including insight facets (computeInsightFacets), artifact discovery (discoverArtifacts), bounded tail/head reads (readSessionTail / readSessionHead), the streaming line renderer (makeStreamRenderer), and the HTML document renderers (renderSessionHtmlDocument, renderTrajectoryHtml). It is pure and read-only — no process spawn, no network, no writes.

The backup subpath (@phnx-labs/sessions-cli/backup) carries the off-box backup library behind --to-r2 / --from-r2: the managed and bring-your-own clients, key escrow, uploadBackup / pullBackup, and the managed store's Worker source (renderSessionsWorkerScript) that agents sessions backup-setup deploys. Unlike reader, it talks to the network and caches a key.

See PHNX-4012 and PHNX-4118.