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

bash-gres

v2.8.3

Published

PostgreSQL-backed virtual filesystem with bash command interface

Downloads

2,725

Readme

bash-gres

PostgreSQL-backed virtual filesystem for AI agents. Implements the just-bash IFileSystem interface, so you can pass it directly to new Bash({ fs }) and get a complete bash environment backed by PostgreSQL.

Features

  • Full bash environment via just-bash: 60+ commands, pipes, redirects, variables, loops
  • Node.js fs-compatible API: readFile, writeFile, mkdir, cp, mv, rm, symlink, stat, walk, glob
  • Workspace isolation via PostgreSQL Row-Level Security
  • Copy-on-write versions per version root: fork, diff, merge, revert, promote, delete
  • Versioned directories via mkdir(path, { versioned: true }) and scoped facades
  • BM25 full-text search via pg_textsearch
  • Optional pgvector semantic and hybrid search
  • Bring your own driver: postgres.js, node-postgres (pg), or Drizzle ORM

Install

npm install bash-gres

Then install your database driver and just-bash:

# postgres.js
npm install postgres just-bash

# node-postgres (pg)
npm install pg just-bash

# Drizzle ORM
npm install drizzle-orm just-bash

Quick Start

import postgres from "postgres"
import { Bash } from "just-bash"
import { setup, PgFileSystem } from "bash-gres/postgres"

const sql = postgres("postgres://localhost:5432/myapp")
await setup(sql)

const fs = new PgFileSystem({ db: sql, workspaceId: "workspace-1" })
const bash = new Bash({ fs })

await bash.exec("mkdir -p /project/src")
await bash.exec('echo "hello world" > /project/src/index.ts')
await bash.exec("cat /project/src/index.ts")
// { exitCode: 0, stdout: "hello world\n", stderr: "" }

With node-postgres (pg)

import pg from "pg"
import { Bash } from "just-bash"
import { setup, PgFileSystem } from "bash-gres/node-postgres"

const pool = new pg.Pool({ connectionString: "postgres://localhost:5432/myapp" })
await setup(pool)

const fs = new PgFileSystem({ db: pool, workspaceId: "workspace-1" })
const bash = new Bash({ fs })

With Drizzle ORM

import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import { setup, PgFileSystem } from "bash-gres/drizzle"

const sql = postgres("postgres://localhost:5432/myapp")
const db = drizzle(sql)

await setup(db)

const fs = new PgFileSystem({ db, workspaceId: "workspace-1" })

Filesystem API

await fs.writeFile("/docs/guide.md", "# Getting Started")
await fs.mkdir("/docs/images", { recursive: true })
const content = await fs.readFile("/docs/guide.md")
const entries = await fs.readdir("/docs")

// Slice large files server-side
const bytes = await fs.readFileRange("/log.txt", { offset: 0, limit: 1024 })
const { content: head, total } = await fs.readFileLines("/log.txt", { offset: 1, limit: 50 })

await fs.cp("/docs", "/backup", { recursive: true })
await fs.mv("/backup/guide.md", "/archive/guide.md")
await fs.rm("/archive", { recursive: true, force: true })

await fs.symlink("/docs/guide.md", "/latest")
const stat = await fs.stat("/docs/guide.md")
const tree = await fs.walk("/docs")

Workspace Usage

const usage = await fs.getUsage()
const projectUsage = await fs.getUsage({ path: "/project" })

usage.logicalBytes     // visible file + symlink bytes in fs.version
usage.referencedBlobBytes // deduplicated blob bytes referenced by visible files
usage.storedBlobBytes  // deduplicated blob bytes stored for the workspace
usage.blobCount        // stored blob rows
usage.versions         // version labels in the active version root
usage.entryRows        // fs_entries rows in the active version root, including tombstones
usage.visibleNodes     // visible nodes in fs.version, including root
usage.limits           // { maxFiles, maxFileSize, maxWorkspaceBytes? }

Set maxWorkspaceBytes to enforce a deduplicated blob-storage quota per workspace:

const fs = new PgFileSystem({
  db: sql,
  workspaceId: "tenant-a",
  maxWorkspaceBytes: 100 * 1024 * 1024,
})

try {
  await fs.writeFile("/large.bin", bytes)
} catch (e) {
  if (e instanceof FsQuotaError) {
    e.code            // "ENOSPC"
    e.limit           // configured maxWorkspaceBytes
    e.current         // current stored blob bytes
    e.attemptedDelta  // bytes for the new unique blob
  }
}

Versioning

Each PgFileSystem instance is bound to a version (default "main") inside an active version root. By default the version root is /, preserving workspace-wide versioning. You can also make any non-nested directory versionable and work through a scoped facade rooted at that directory.

Versions are copy-on-write overlays: the same path can hold different contents, and fork() is O(1) because it links the new version to its parent through a closure table without copying entry rows. Reads walk that closure to the nearest ancestor with a row at the requested path.

This is a live ancestor overlay, not a historical snapshot. A write to a parent version after a child has been forked can still affect the child's visible view at any path the child has not shadowed. Once the child writes (or deletes) a path, that path is shielded from later parent writes. To freeze a checkpoint independent of its parents, fork and then detach().

const v1 = new PgFileSystem({ db: sql, workspaceId: "app", version: "v1" })
await v1.writeFile("/config.json", '{"env":"staging"}')

const v2 = await v1.fork("v2")                 // O(1) link, no row copy
await v2.writeFile("/config.json", '{"env":"prod"}')

await v1.readFile("/config.json") // '{"env":"staging"}' (untouched)
await v2.readFile("/config.json") // '{"env":"prod"}'

await v1.listVersions()     // ["v1", "v2"]
await v1.deleteVersion("v2") // drops every row in v2

Versioned Directories

Use mkdir(path, { versioned: true }) to make a directory an independent version root, similar to running git init inside that directory. The directory remains a normal filesystem directory, but version operations on its scoped facade only affect that subtree.

const fs = new PgFileSystem({ db: sql, workspaceId: "app" })
await fs.init()

await fs.mkdir("/database", { versioned: true })

const dbMain = await fs.versioned("/database")
await dbMain.writeFile("/schema.sql", "main")

const dbDraft = await dbMain.fork("draft")
await dbDraft.writeFile("/schema.sql", "draft")

await dbMain.readFile("/schema.sql")  // "main"
await dbDraft.readFile("/schema.sql") // "draft"
await dbMain.listVersions()           // ["draft", "main"]

Version labels are scoped to the versioned directory, so /database and /user can both have a draft version. Nested versioned directories are rejected.

Versioning primitives include:

  • diff(other, { path? }), diffCount(other, { path?, nodeType? }), and diffStream(other, { path?, batchSize? }) to compare visible trees.
  • merge(source, { strategy?, paths?, pathScope?, dryRun? }) for LCA-based three-way merges.
  • cherryPick(source, paths) to source-win copy selected paths without LCA conflict checks.
  • revert(target, { paths?, pathScope? }) to restore selected paths to another version.
  • detach() to materialize a version into a standalone snapshot independent of ancestors.
  • renameVersion(label, { swap? }) and promoteTo(label, { dropPrevious? }) for deploy labels.
  • listHistory({ limit?, cursor?, includeChanges?, includeRoot?, path? }) to walk ancestor history with keyset pagination, plus versionDiff(versionId, { path? }) and versionDiffStream(versionId, { path?, batchSize? }) to fetch the diff for a single history entry.
  • sweepHistory() to physically flatten retained history into self-contained snapshots and GC orphan blobs.

The "live" version is caller-side: BashGres exposes versions as data, your app decides which one the runtime reads from. A typical deploy flow is fork() a draft, edit it, optionally merge() or revert() changes, then promoteTo("live"). See bashgres.com/docs/versioning for the full versioning guide.

Browsing history

listHistory() returns ancestor versions paginated by depth from the current version backwards. includeChanges controls how much per-entry detail comes back: false (default, just metadata), "paths" (cheap path + change-kind summary), or true (full before/after shapes). All three modes share a single batched query, so paths-mode and full-changes mode are within ~5% of each other on large pages.

const fs = new PgFileSystem({
  db: sql,
  workspaceId: "app",
  version: "main",
  historyRetention: "retain",   // keep deleted versions in history
})

// 1. List page metadata + per-row "what changed" summary.
const page = await fs.listHistory({ limit: 20, includeChanges: "paths" })
for (const entry of page.entries) {
  console.log(entry.version, entry.createdAt, entry.changes.length, "changes")
  // entry.changes: [{ path, change: "added"|"removed"|"modified"|"type-changed" }]
}
if (page.nextCursor) { /* fetch next page with { cursor: page.nextCursor } */ }

// 2. Click an entry → full diff against its parent (works for root and
//    deleted-but-retained entries too).
const detail = await fs.versionDiff(page.entries[0]!.versionId)
// detail: VersionDiffEntry[] with full before/after shapes

// 3. Or stream large diffs page-by-page.
for await (const change of fs.versionDiffStream(page.entries[0]!.versionId, { batchSize: 100 })) {
  // ...
}

historyRetention: "retain" keeps deleted version rows visible in history (with deletedAt !== null); the default "discard" physically removes them. Run sweepHistory() to compact a retain-mode workspace back into self-contained snapshots and GC blobs no live entry references.

Search

// Full-text search (BM25)
const results = await fs.textSearch("machine learning", {
  path: "/docs",
  limit: 20,
})

// Semantic search (pgvector)
const similar = await fs.semanticSearch("how do LLMs work", {
  path: "/docs",
  limit: 10,
})

// Hybrid: BM25 + vector combined
const hybrid = await fs.hybridSearch("transformer architecture", {
  path: "/docs",
  textWeight: 0.4,
  vectorWeight: 0.6,
})

Requirements

  • PostgreSQL 15+ with the ltree extension
  • Node.js 18+
  • Optional: pg_textsearch for BM25 full-text search
  • Optional: pgvector for semantic/hybrid search

Subpath Exports

bash-gres                PgFileSystem, setup(), search, types
bash-gres/postgres       postgres.js adapter (setup, PgFileSystem, createPostgresClient)
bash-gres/node-postgres  node-postgres (pg) adapter (setup, PgFileSystem, createNodePgClient)
bash-gres/drizzle        Drizzle adapter (setup, PgFileSystem, createDrizzleClient, createSchema)

Development

docker compose up -d   # start postgres on localhost:5434
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5434/bashgres_test npm test
npm run typecheck      # type check
npm run build          # compile to dist/

Docs

Full documentation at bashgres.com/docs.

License

MIT