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

ai-hist

v0.45.0

Published

Local history ingestion, storage, evidence, SDK, CLI and MCP server.

Readme

ai-hist

The local history TypeScript SDK, Node CLI, and MCP server. Storage and local ingestion use one mandatory ai-hist-native Node-API engine. Optional source and destination plugins compose through public interfaces; no JavaScript SQL or commercial dependency is required by this package.

npm install ai-hist
import {
  CATALOG_SOURCES,
  discoverSessions,
  hydrateSession,
  listSessionCatalogPage,
  getSession,
  getSessionEventsPage,
  sessionEvents,
  getSessionToolCallsPage,
  sessionToolCalls,
  getSessionFileEditsPage,
  sessionFileEdits,
  search,
  recent,
  stats,
  sync,
} from 'ai-hist';

// Runtime validation stays in step with the sources supported by this build.
for (const source of CATALOG_SOURCES) console.log(source);

await discoverSessions({ scope: 'all', limit: 100 });
const page = await listSessionCatalogPage({ scope: 'all', limit: 20 });
const first = page.sessions[0];
if (first) {
  await hydrateSession({ source: first.source, sessionId: first.sessionId });
  const prompts = await getSession(first.sessionId);
  const events = await getSessionEventsPage(first.sessionId, { limit: 200 });
  for await (const event of sessionEvents(first.sessionId)) consume(event);
  const tools = await getSessionToolCallsPage(first.source, first.sessionId, { limit: 200 });
  const edits = await getSessionFileEditsPage(first.source, first.sessionId, { limit: 200 });
  for await (const call of sessionToolCalls(first.source, first.sessionId)) consume(call);
  for await (const edit of sessionFileEdits(first.source, first.sessionId)) consume(edit);
}

All APIs are async. listSessionCatalog and listSessionCatalogPage are cache-only. discoverSessions is shallow discovery. hydrateSession is targeted evidence acquisition for one existing catalog row. It returns hydrated, updated, unchanged, or capability_limited, an indexed source stamp, evidence counts, related native session IDs, and bounded-work metrics. Targeted remote hydration uses scope: 'remote' and an explicitly configured source plugin registry: Claude web sessions can return capability: 'full' after complete teleport evidence is acquired; Codex cloud tasks return partial when their supported unified diff is indexed or shallow_only when no richer evidence is exposed. Partial results retain discoveryState: 'shallow'. sync is full ingestion. Missing databases return empty read results; they do not trigger provider I/O.

SOURCES and CATALOG_SOURCES are immutable runtime registries corresponding to the exported Source and CatalogSource types. Use isSource(value) or isCatalogSource(value) to validate untyped input without maintaining a copied provider list. CATALOG_SOURCES is the same list as SOURCES: every source can identify a catalog entry.

Session discovery, listing, search, recent history, statistics, and sync accept scope: 'local' | 'remote' | 'all'. Scope defaults to local, preserving offline behavior and making provider-cloud access explicit. The CLI exposes the same mutually exclusive --local, --remote, and --all flags; omitting them is equivalent to --local.

Cached reads preserve the requested scope and never consult commercial auth. Stored remote history can be queried with absent, malformed, expired, or ambiguous credentials. Remote acquisition requires an explicitly loaded plugin registry. Install @relayhistory/provider-sources for Claude web/Codex cloud. Installing a package does not register it or inspect auth.

import { HistoryPluginRegistry } from 'ai-hist';
import { createHistoryPlugin } from '@relayhistory/provider-sources';
const plugins = new HistoryPluginRegistry();
plugins.register(createHistoryPlugin({connectors:['claude-web']}));
await discoverSessions({ scope:'remote', plugins, sourceConnectors:['claude-web'] });
await sync({ scope:'all', plugins, sourceConnectors:[] }); // local adapters only
const cached = await stats({ scope:'remote' }); // no login required

The CLI accepts --config history.json on acquisition commands. Config lists explicit plugin modules/options; --source-connector selects their IDs and is repeatable. --no-source-connectors disables remote acquisition. MCP uses AI_HIST_PLUGIN_CONFIG and source_connectors. Scope defaults to local even with plugins configured. See source plugins. Native contract 14 rejects old addons that lack the source/delivery boundary.

Catalog pages, discovery results, statistics, and sync results echo the requested scope, and discovery results additionally report locationsRun — the connector locations that actually executed. History and catalog rows have locations, containing local, remote, or both, so an all query still returns one logical session while preserving where it was found. resumeCommand() returns null for a remote-only history row rather than emitting a local CLI command; an empty locations array retains legacy local behavior for rows written before provenance tracking.

The event primitive is page-based and uses { tsMs, id } as a deterministic cursor. The sessionEvents async iterator walks pages without accumulating a large transcript. getSessionEvents is an explicit collecting convenience.

Tool calls and file edits follow the same page / iterator / collect trio (getSessionToolCallsPage, sessionToolCalls, getSessionToolCalls, and the ...FileEdit... equivalents), and take both a source and a session ID because provider session IDs are not unique across providers. The source is half of that identity, so one outside the Source set raises InvalidArgumentError instead of reading as an empty session. Their cursor is { tsMs: number | null, id: number }: a record may be indexed without a timestamp, and undated records are ordered last. Feeding a cursor back in accepts either spelling of that tail — tsMs: null as emitted, or no tsMs at all after a transport that drops nulls — so a printed or JSON-serialized cursor returns unedited. Stored provider JSON is parsed into args and structuredPatch; a value that is absent or unparseable becomes null while the raw string stays available as argsJson and structuredPatchJson, so an unreadable payload never fails a page. parseStoredJson(raw) is that same parse, exported for callers that hold a raw stored string of their own: it returns the parsed value, or null for anything that is not a parseable string.

Session topology

Sessions that delegate to subagents form a tree, and it is queryable:

import {
  getSessionRelationships,
  getSessionTree,
  sessionEventsIncludingDescendants,
} from 'ai-hist';

const { asParent, asChild, continuity, capabilities } = await getSessionRelationships({
  source: 'codex',
  sessionId: rootId,
});
const tree = await getSessionTree({ source: 'codex', sessionId: rootId, maxDepth: 8 });

for await (const event of sessionEventsIncludingDescendants({ source: 'codex', sessionId: rootId })) {
  // event.sessionId is the session that actually produced the event: a
  // child's event is never rewritten as the parent's.
  consume(event);
}

Each SessionRelationship reports its identityStatus. observed means the provider named the child, so childSessionId is a real session id and childHasEvents says whether its events are independently addressable through getSessionEventsPage. unlinked means the provider recorded a delegation but no stable child identity: childSessionId is null, the child's output stays attributed to the parent, and the row keeps the evidence (evidenceKind, evidenceLocator, evidenceRef) that established it. An identity is never synthesized.

capabilities.stableChildIdentity tells you what to expect from the provider before you read a single row: always for Codex, sometimes for Claude — only provider versions that emit a per-child agentId name the child — and never for the remaining sources.

getSessionTree returns pre-order nodes (the root first), the unlinked evidence found at any depth, and diagnostics. nodes[0] is always the root, so a session with no children — and a database that does not exist yet — comes back as a one-node tree rather than an empty one. Children are ordered by (spawnedAtMs, relationshipUid) with null spawn times last, so repeated calls against the same database return identical results.

Traversal visits each session once, at the position pre-order first reaches it. An edge back into the current branch's ancestry is a cycle and emits a RELATIONSHIP_CYCLE diagnostic instead of looping; an edge to a session already emitted on another branch is a diamond and is quietly not expanded twice. maxDepth (default 32, maximum 64) and maxNodes (default 1000, maximum 10000) bound the work and set truncated with a RELATIONSHIP_TREE_DEPTH_LIMIT or RELATIONSHIP_TREE_TRUNCATED diagnostic rather than returning a silently short tree. Tree-level truncated means a budget cut the walk short — a cycle or diamond never sets it — while a node's own truncated marks children it did not expand.

For large topologies, getSessionChildrenPage is the keyset primitive and sessionDescendants is the async iterator over it, walking descendants breadth-first without materializing a tree. Its maxNodes budget has the same 1,000 default and 10,000 maximum as getSessionTree, and includes the root. It yields the nodes getSessionTree emits minus the root, with the same childCount (linked children only, at the depth boundary included) and the same truncated rule. The order differs: the walker is breadth-first, the tree is pre-order, so a consumer that depends on either the root node or getSessionTree's ordering has to account for that. sessionEventsIncludingDescendants applies its limit to each session it reads, not to the iteration as a whole.

Continuity

Resumed, forked and continued sessions are a second kind of relationship: continuation, fork and resume, reported on getSessionRelationships's own continuity array rather than mixed into asParent / asChild. Each row carries originSessionId — the conversation it branched from, when the provider named one distinct from the parent — and an evidenceRef naming the signal that produced it: the provider field (continuedFromSessionId, forkSessionId), resume-marker for a /resume the human typed, sharedSessionId for two transcripts carrying one provider session id, or the record uuid that linked two files.

getSessionTree and getSessionChildrenPage take relationshipKinds. Omitting it follows delegation edges only, so existing calls answer exactly as before; naming the continuity kinds expands from an origin to its resumed, continued, or forked descendants:

const lineage = await getSessionTree({
  source: 'claude',
  sessionId: originId,
  relationshipKinds: ['continuation', 'fork', 'resume'],
});

Two transcripts carrying the same provider session id are branches with no identity of their own, so each is an unlinked row keyed on its transcript: a branch's identity is never taken from its file name. Evidence that cannot resolve yet — a parent record no session has indexed, a lone branch with no sibling — is reported as a RELATIONSHIP_CONTINUITY_UNRESOLVED diagnostic on both hydrateSession and getSessionRelationships, and resolves on its own once the session holding the missing record is hydrated.

Native loading failures distinguish unsupported platforms, missing optional platform packages, addon load failures, SDK/native contract mismatches, and database open failures through stable RelayHistoryError subclasses. Provider capability failures use UnsupportedOperationError. Targeted remote hydration also distinguishes connector configuration, expired authentication, partial evidence, and connector/parser failures with dedicated error subclasses.

The old synchronous AiHist class and openAiHist() API were removed in 1.0. See the migration guide.

Export

The local package has no cloud exports, login CLI or cloud MCP tool. Team uploads come from the Agent Relay desktop app. Git hooks and commit linking remain local SDK operations.

The MCP-only list_relay_agents tool lists live relay participants, not history. relay_status reports whether the local session hosting the MCP server is reachable; join_relay makes that session reachable by teammates and agents, with optional public name and description; and leave_relay removes it. They make short local Unix-socket requests to Agent Relay desktop. Roster listing accepts optional query, where (this_computer, cloud, or other_desktop), and include_idle inputs. Socket discovery checks AGENT_RELAY_SOCKET, then ~/.agentworkforce/desktop/relay-socket, then the macOS/Linux defaults. No Relaycast client or cloud credential is part of ai-hist; when the desktop is absent the tool returns a clear non-fatal result. A connected desktop has a 15-second response deadline. If it does not answer in that time, the tool reports timeout distinctly from an absent app. For status, join, and leave, the same missing-desktop result remains non-fatal, and a disabled self-registration setting is returned as readable not_allowed guidance. A mutation that times out after being sent reports an indeterminate result and directs the caller to relay_status.

Use exportHistory(selection) for a bounded historical snapshot or ai-hist export --selection selection.json for NDJSON stdout. See export.

Source discovery and hydration accept acquisitionTimeoutMs for each selected connector operation, including a complete paginated snapshot. The default is 300,000 ms; choose an integer from 1 through 3,600,000 ms. CLI acquisitions accept --acquisition-timeout-ms N; MCP acquisitions accept acquisition_timeout_ms. The same budget and cancellation signal reach the optional source helper. Timeouts return SOURCE_ACQUISITION_TIMEOUT, cancellation returns SOURCE_ACQUISITION_CANCELLED, and neither commits a partial snapshot. Typed source failures such as AUTHENTICATION_EXPIRED and SESSION_NOT_FOUND retain their public classes/codes with sanitized messages.