Skip to content

taowang1993/gitfs

Repository files navigation

GitFs

CI npm version license: MIT

Enable AI to browse Git repos like local folders.

GitFs is a small read-only, git-backed filesystem adapter designed for AI tooling and just-bash.

It preloads a repo manifest for a specific commit, exposes the tree as normal filesystem paths, loads file blobs lazily, and caches both manifests and blobs by commit SHA.

What this repo implements

  • provider-agnostic GitRepoFilesystem
  • in-memory GitFsCache
  • persistent disk-backed PersistentGitFsCache
  • subtree mounting (docs/content/en can become /)
  • lazy blob reads
  • symlink support
  • GitHub provider using commit/tree/blob APIs
  • GitLab provider using commit/tree/blob APIs
  • Gitea provider using commit/tree/blob APIs
  • TTL-aware in-memory and persistent cache pruning
  • a read-only API shaped like the just-bash filesystem interface
  • a tiny local browsing CLI

Why

just-bash works best when content looks like a normal folder tree.

Instead of teaching an agent how to clone a repo, call a host-specific API, and guess file paths, GitFs lets the agent use predictable paths with filesystem operations like:

  • ls
  • find
  • rg
  • cat
  • glob traversal

Install

From npm

Install the published package with:

npm install @taowang1993/gitfs

Note: the unscoped gitfs package on npm is a different project. Use @taowang1993/gitfs for this library.

Core GitFs repo

GitFs itself uses only Node.js built-ins at runtime.

This repo now uses TypeScript source files and runs them directly on Node.js 22+ via --experimental-strip-types.

If you are working in this repo directly:

npm install
npm run typecheck
npm run typecheck:tests
npm test
npm run build

Distribution output

This project now ships compiled output in dist/ for non-Node-22 consumers.

The build emits:

  • compiled JavaScript
  • .d.ts type declarations
  • declaration maps
  • source maps

That means:

  • repo development can run .ts source directly on Node 22+
  • package consumers can use compiled dist/*.js + dist/*.d.ts

The repo also includes a prepare script so installs from a git checkout can build dist/ automatically after dependencies are installed.

You can run a full local verification with:

npm run check

And a release-oriented verification with:

npm run release:check

GitFs + just-bash

If you want to mount GitFs inside just-bash, install just-bash in the application or example package that will run the integration.

The core GitFs package does not need just-bash as a hard dependency.

Typical app-level install:

npm install just-bash

If GitFs is consumed as a local workspace package, install just-bash in the consuming app and import GitFs from your workspace/local package.

Basic usage

From npm

import {
  GitHubProvider,
  GitRepoFilesystem,
  PersistentGitFsCache,
} from "@taowang1993/gitfs";

const provider = new GitHubProvider({
  owner: "vercel",
  repo: "ai",
  token: process.env.GITHUB_TOKEN,
});

const fs = await GitRepoFilesystem.create({
  provider,
  ref: "main",
  root: "content/docs",
  cache: new PersistentGitFsCache({ dir: ".gitfs-cache" }),
});

console.log(fs.info());
console.log(await fs.readdir("/"));
console.log(await fs.readFile("/index.mdx"));

From this repo during development

import {
  GitHubProvider,
  GitRepoFilesystem,
  PersistentGitFsCache,
} from "./src/index.ts";

const provider = new GitHubProvider({
  owner: "vercel",
  repo: "ai",
  token: process.env.GITHUB_TOKEN,
});

const fs = await GitRepoFilesystem.create({
  provider,
  ref: "main",
  root: "content/docs",
  cache: new PersistentGitFsCache({ dir: ".gitfs-cache" }),
});

just-bash integration

GitRepoFilesystem is deliberately duck-typed to match the just-bash filesystem contract.

just-bash is optional. You only install it if you want the mounted bash workflow.

What to install

If your app wants the GitFs + just-bash flow, install just-bash in that app:

npm install just-bash

Agent skill included

This repo includes an agent skill that helps coding agents wire GitFs + just-bash into an AI agent:

  • skills/gitfs-just-bash-integration/SKILL.md

Use that skill when you want an agent to scaffold or document:

  • provider setup
  • cache setup
  • /repo + /workspace mount layout
  • just-bash integration code
  • tests and docs for the integration

You do not need to install just-bash just to use:

  • GitRepoFilesystem
  • provider adapters
  • cache layers
  • the GitFs CLI

Recommended integration flow

  1. Create a Git provider.
  2. Create a GitRepoFilesystem snapshot.
  3. Mount it at a read-only location such as /repo.
  4. Mount a separate writable workspace such as /workspace.
  5. Start just-bash with cwd: "/workspace".
  6. Let the agent search /repo and write artifacts into /workspace.

Example

import { Bash, InMemoryFs, MountableFs } from "just-bash";
import { ReadWriteFs } from "just-bash/fs/read-write-fs";
import {
  GitHubProvider,
  GitRepoFilesystem,
  PersistentGitFsCache,
} from "@taowang1993/gitfs";

const provider = new GitHubProvider({
  owner: "my-org",
  repo: "knowledge-base",
  token: process.env.GITHUB_TOKEN,
});

const repoFs = await GitRepoFilesystem.create({
  provider,
  ref: "main",
  root: "docs",
  cache: new PersistentGitFsCache({ dir: ".gitfs-cache" }),
});

const fs = new MountableFs({ base: new InMemoryFs() });
fs.mount("/repo", repoFs);
fs.mount("/workspace", new ReadWriteFs({ root: "/tmp/agent-workspace" }));

const bash = new Bash({ fs, cwd: "/workspace" });

await bash.exec("find /repo -name '*.md' | head -20");
await bash.exec("cp /repo/getting-started.md /workspace/getting-started.md || true");
await bash.exec("rg 'TODO|FIXME' /repo || true");

Why mount a separate workspace?

GitFs is read-only by design.

That means a good default layout is:

  • /repo → GitFs snapshot of the remote repo
  • /workspace → writable area for notes, patches, generated files, and copied artifacts

This matches the guidance from just-bash: keep trusted/runtime code separate from guest-writable workspace paths.

Architecture

1. Manifest layer

A provider returns git tree entries for a resolved commit SHA. GitFs builds an in-memory manifest with normalized POSIX paths.

2. Blob layer

File and symlink contents are fetched on first read via provider.getBlob().

3. Cache layer

  • manifests: provider + commit SHA + subtree
  • blobs: provider + commit SHA + blob SHA

Branch names are only used to resolve a commit. Caching is keyed by the resolved commit SHA.

Providers

GitHub provider

GitHubProvider uses:

  • GET /repos/:owner/:repo/commits/:ref
  • GET /repos/:owner/:repo/git/trees/:treeSha?recursive=1
  • GET /repos/:owner/:repo/git/blobs/:sha

If GitHub returns a truncated recursive tree, the provider throws instead of building an incomplete manifest.

GitLab provider

GitLabProvider uses:

  • GET /projects/:project/repository/commits/:ref
  • GET /projects/:project/repository/tree?ref=:commitSha&recursive=true
  • GET /projects/:project/repository/blobs/:sha/raw

The GitLab tree loader follows X-Next-Page headers so paginated trees are merged into a single manifest.

Gitea provider

GiteaProvider uses:

  • GET /repos/:owner/:repo/commits/:ref
  • GET /repos/:owner/:repo/git/trees/:treeSha?recursive=1
  • GET /repos/:owner/:repo/git/blobs/:sha

Persistent cache backend

PersistentGitFsCache stores manifests and blobs on disk so later processes can reuse commit snapshots without re-fetching the tree or blob data.

import { PersistentGitFsCache } from "./src/index.ts";

const cache = new PersistentGitFsCache({ dir: ".gitfs-cache" });

Both cache implementations also support TTL-based expiry and pruning:

import { GitFsCache, PersistentGitFsCache } from "./src/index.ts";

const memoryCache = new GitFsCache({ manifestTtlMs: 60_000, blobTtlMs: 300_000 });
await memoryCache.pruneExpired();

const diskCache = new PersistentGitFsCache({
  dir: ".gitfs-cache",
  manifestTtlMs: 60_000,
  blobTtlMs: 300_000,
});
await diskCache.pruneExpired();

Tiny browsing CLI

A tiny CLI is included for local snapshot browsing.

Dev CLI from source

npm run cli -- \
  --provider github \
  --owner vercel \
  --repo ai \
  --ref main \
  --root content/docs \
  ls /

Dist CLI after build

npm run build
npm run cli:dist -- \
  --provider github \
  --owner vercel \
  --repo ai \
  --ref main \
  --root content/docs \
  ls /

Other commands:

  • info
  • ls [path]
  • cat <path>
  • stat <path>
  • paths [needle]
  • find [needle]
  • readlink <path>

CI / release automation

This repo includes GitHub Actions workflows for:

  • CI verification on pushes and pull requests
  • release checks on tags / manual dispatch
  • tarball creation
  • npm publish via trusted publishing from GitHub Actions
  • GitHub release creation from version tags

Trusted publishing setup

Based on npm's documentation, this project should publish with trusted publishing, not a long-lived npm token.

Why:

  • it bypasses interactive 2FA correctly
  • it is npm's recommended path for CI/CD publishing
  • it uses GitHub OIDC instead of a long-lived write token
  • npm automatically generates provenance for public packages from public GitHub repos

Configure npm trusted publishing for @taowang1993/gitfs with these values:

  • Organization or user: taowang1993
  • Repository: gitfs
  • Workflow filename: release.yml
  • Environment name: leave blank unless you add a GitHub environment later

The workflow is already configured for the npm docs requirements:

  • GitHub-hosted runner
  • permissions.id-token: write
  • tag-triggered publish flow
  • npm publish --access public

After trusted publishing is configured on npm, no NPM_TOKEN secret is needed for publishing this package.

Important note about first publish

npm's trusted publishing docs describe configuration from the package's npm settings page. If npm does not let you configure trusted publishing for an unpublished package yet, we may need a one-time manual/bootstrap publish first.

If that happens, the fallback is:

  • create a granular npm access token
  • enable bypass 2FA
  • use it only for the initial publish
  • then immediately switch the package to trusted publishing and revoke the token

Release versioning

Use npm versioning so git tags and package versions stay aligned:

npm run version:patch
# or
npm run version:minor
# or
npm run version:major

Then push the commit and tag:

git push origin main --follow-tags

The release workflow is triggered by v* tags and can publish to npm plus create a GitHub release.

After trusted publishing is configured on npm, publishing should happen from GitHub Actions without any npm token secret.

If a one-time bootstrap publish is required before trusted publishing can be configured, publish locally only after authenticating successfully:

npm login
npm whoami
npm publish --access public

Example app / demo

A real just-bash demo app is included at:

  • examples/just-bash-demo/app.ts
  • examples/just-bash-demo/run-with-just-bash.mts

Run it after installing the example's dependencies and setting the required env vars:

cd examples/just-bash-demo
npm install
GITFS_GITHUB_OWNER=vercel \
GITFS_GITHUB_REPO=ai \
GITHUB_TOKEN=... \
npm start

The core GitFs library still does not require just-bash; only the demo package/app that wants the integration does.

Current limitations

  • read-only only
  • no GitBucket adapter yet
  • submodules are skipped and reported as manifest warnings
  • no provider-specific auth helpers beyond token/header support

Local checkout guidance

If the repo already exists on disk, the simplest path is still just-bash + OverlayFs.

GitFs is mainly for the remote-only case where you still want filesystem-shaped browsing.

About

Enable AI to browse Git repos like local folders.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors