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

@procedures/git

v0.1.0

Published

Git as 26 typed procedures: status, diff, log, add, commit, push, pull, branch, checkout, stash and more, run with argv, async and abortable.

Readme

@procedures/git

npm version License: MIT

Git operations as RPC procedures - git.add, git.commit, git.push, git.status, and 22 more git commands exposed as type-safe procedures.

Overview

@procedures/git exposes comprehensive Git functionality as RPC procedures through the client system. It provides 26 procedures covering all major Git operations including repository state management, staging, commits, branching, remote operations, stashing, and repository predicates.

This package wraps Git commands directly (not through client-shell), providing structured input/output with full type safety via Zod schemas.

Architecture

graph TB
    A[client-git] -->|depends on| B[@procedures/client]
    A -->|validates with| C[zod]

    D[Application] -->|calls| E[git.* procedures]
    E -->|executes| F[Git CLI]
    E -->|parses| G[Structured Output]

    H[Repository State<br/>status, log, diff, branch] -.-> E
    I[Staging & Commits<br/>add, commit, checkout] -.-> E
    J[Remote Sync<br/>push, pull, fetch, clone] -.-> E
    K[Stash Operations<br/>stash.*, export, import] -.-> E
    L[Predicates<br/>hasChanges, isClean, etc] -.-> E

    style A fill:#e1f5ff
    style E fill:#e1ffe1

Procedure Categories

graph LR
    A[client-git<br/>26 Procedures] --> B[Repository State<br/>5 procedures]
    A --> C[Staging & Commits<br/>3 procedures]
    A --> D[Remote Operations<br/>5 procedures]
    A --> E[Predicates<br/>6 procedures]
    A --> F[Stash Operations<br/>7 procedures]

    B -.-> B1[status, log, diff,<br/>branch, init]
    C -.-> C1[add, commit,<br/>checkout]
    D -.-> D1[push, pull, fetch,<br/>clone, remote]
    E -.-> E1[hasChanges, isClean,<br/>hasStagedChanges,<br/>hasUnstagedChanges,<br/>hasUntrackedFiles,<br/>hasLocalCommits]
    F -.-> F1[stash.list, stash.push,<br/>stash.pop, stash.apply,<br/>stash.drop, stash.export,<br/>stash.import]

    style A fill:#e1f5ff
    style B fill:#ffe1e1
    style C fill:#ffe1e1
    style D fill:#ffe1e1
    style E fill:#fff4e1
    style F fill:#e1ffe1

Installation

# Using pnpm (recommended)
pnpm add @procedures/git

# Using npm
npm install @procedures/git

# Using yarn
yarn add @procedures/git

Quick Start

import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import "@procedures/git"; // registers its procedures

const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));

// Get repository status
const status = await client.exec(["git", "status"], {
  cwd: "/path/to/repo",
});
console.log(status.branch, status.files);

// Stage and commit
await client.exec(["git", "add"], { all: true });
await client.exec(["git", "commit"], { message: "feat: add new feature" });

// Push to remote
await client.exec(["git", "push"], { remote: "origin" });

API Reference

Repository State Procedures

git.status

Get current repository status including branch, tracking info, and file changes.

Path: ["git", "status"]

interface GitStatusInput {
  /** Working directory (default: process.cwd()) */
  cwd?: string;
  /** Also give `lines` in the format of `git status --short` (default: false) */
  short?: boolean;
}

interface GitStatusFile {
  /** The path (for a rename or a copy, the new path). Paths are exact: no quotes. */
  path: string;
  /** For a rename or a copy, the old path */
  from?: string;
  status: "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored";
  staged: boolean;
}

interface GitStatusOutput {
  branch: string;
  ahead: number;
  behind: number;
  files: GitStatusFile[];
  clean: boolean;
  /** With `short: true`: one line per file, for example "R  old.ts -> new.ts" */
  lines?: string[];
}

Example:

const status = await client.exec(["git", "status"], { cwd: "./my-repo" });
console.log(`On branch ${status.branch}`);
console.log(`${status.ahead} commits ahead, ${status.behind} behind`);
console.log(`${status.files.length} changed files`);

git.log

Show commit history.

Path: ["git", "log"]

interface GitLogInput {
  /** Number of commits to show (default: 10) */
  count?: number;
  /** Also give `lines`: one line per commit, the short hash and the subject (default: false) */
  oneline?: boolean;
  /** Branch/tag/commit to start from (default: HEAD) */
  ref?: string;
  /** Working directory */
  cwd?: string;
}

interface GitLogCommit {
  hash: string;
  shortHash: string;
  author: string;
  email: string;
  date: string;
  message: string;
}

interface GitLogOutput {
  commits: GitLogCommit[];
  /** With `oneline: true` */
  lines?: string[];
}

Example:

const log = await client.exec(["git", "log"], { count: 5 });
for (const commit of log.commits) {
  console.log(`${commit.shortHash} - ${commit.message}`);
}

git.diff

Show changes between commits, branches, or working directory.

Path: ["git", "diff"]

interface GitDiffInput {
  /** Show staged changes (default: false, shows unstaged) */
  staged?: boolean;
  /** Compare against ref (branch/commit) */
  ref?: string;
  /** Specific paths to diff */
  paths?: string[];
  /** Show stat summary only (default: false) */
  stat?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitDiffFile {
  /** The path (for a rename, the new path) */
  path: string;
  /** For a rename, the old path */
  from?: string;
  additions: number;
  deletions: number;
}

interface GitDiffOutput {
  files: GitDiffFile[];
  additions: number;
  deletions: number;
  diff?: string;
}

Example:

// Show unstaged changes
const diff = await client.exec(["git", "diff"], { stat: true });
console.log(`${diff.additions} additions, ${diff.deletions} deletions`);

// Show staged changes
const staged = await client.exec(["git", "diff"], { staged: true });

git.branch

Branch operations - list, create, delete branches.

Path: ["git", "branch"]

interface GitBranchInput {
  /** Branch name (for create/delete) */
  name?: string;
  /** Delete the branch (default: false) */
  delete?: boolean;
  /** List branches (default: true if no name provided) */
  list?: boolean;
  /** Include remote branches in list (default: false) */
  remote?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitBranchInfo {
  name: string;
  current: boolean;
  remote: boolean;
  tracking?: string;
}

interface GitBranchOutput {
  branches?: GitBranchInfo[];
  created?: string;
  deleted?: string;
  current: string;
}

Example:

// List all branches
const branches = await client.exec(["git", "branch"], { list: true });
console.log("Branches:", branches.branches.map(b => b.name));

// Create new branch
await client.exec(["git", "branch"], { name: "feature/new-feature" });

// Delete branch
await client.exec(["git", "branch"], { name: "old-feature", delete: true });

git.init

Initialize a new git repository.

Path: ["git", "init"]

interface GitInitInput {
  /** Directory to initialize (default: process.cwd()) */
  cwd?: string;
  /** Create a bare repository (default: false) */
  bare?: boolean;
  /** Name for the initial branch */
  initialBranch?: string;
}

interface GitInitOutput {
  /** The initialized repository path */
  path: string;
  /** Whether the repo was newly created */
  created: boolean;
}

Staging & Commit Procedures

git.add

Stage files for commit.

Path: ["git", "add"]

interface GitAddInput {
  /** Paths to stage (default: []) */
  paths?: string[];
  /** Stage all changes (default: false) */
  all?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitAddOutput {
  staged: string[];
}

Example:

// Stage specific files
await client.exec(["git", "add"], { paths: ["file1.ts", "file2.ts"] });

// Stage all changes
await client.exec(["git", "add"], { all: true });

git.commit

Create a commit with staged changes.

Path: ["git", "commit"]

interface GitCommitInput {
  /** Commit message */
  message: string;
  /** Automatically stage modified files (default: false) */
  all?: boolean;
  /** Amend the last commit (default: false) */
  amend?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitCommitOutput {
  hash: string;
  message: string;
  author: string;
  date: string;
  /** True if commit was skipped (nothing to commit) */
  skipped?: boolean;
}

Example:

const commit = await client.exec(["git", "commit"], {
  message: "feat: add new feature\n\nDetailed description here",
  all: true, // Auto-stage modified files
});
console.log(`Created commit ${commit.hash}`);

git.checkout

Checkout branch, tag, or restore files.

Path: ["git", "checkout"]

interface GitCheckoutInput {
  /** Branch, tag, or commit to checkout */
  ref: string;
  /** Create branch if it doesn't exist (default: false) */
  create?: boolean;
  /** Specific paths to checkout (default: all) */
  paths?: string[];
  /** Working directory */
  cwd?: string;
}

interface GitCheckoutOutput {
  ref: string;
  created: boolean;
}

Example:

// Checkout existing branch
await client.exec(["git", "checkout"], { ref: "main" });

// Create and checkout new branch
await client.exec(["git", "checkout"], { ref: "feature/new", create: true });

// Restore specific file
await client.exec(["git", "checkout"], { ref: "HEAD", paths: ["file.ts"] });

Remote Operations

git.push

Push commits to remote repository.

Path: ["git", "push"]

interface GitPushInput {
  /** Remote name (default: origin) */
  remote?: string;
  /** Branch to push (default: current branch) */
  branch?: string;
  /** Force push (default: false) */
  force?: boolean;
  /** Set upstream tracking (default: false) */
  setUpstream?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitPushOutput {
  remote: string;
  branch: string;
  commits: number;
}

Example:

// Push to origin
await client.exec(["git", "push"], {});

// Push and set upstream
await client.exec(["git", "push"], {
  remote: "origin",
  branch: "feature/new",
  setUpstream: true,
});

// Force push (use with caution!)
await client.exec(["git", "push"], { force: true });

git.pull

Pull changes from remote repository.

Path: ["git", "pull"]

interface GitPullInput {
  /** Remote name (default: origin) */
  remote?: string;
  /** Branch to pull (default: current branch) */
  branch?: string;
  /** Rebase instead of merge (default: false) */
  rebase?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitPullOutput {
  remote: string;
  branch: string;
  commits: number;
  fastForward: boolean;
}

git.fetch

Fetch from remote without merging.

Path: ["git", "fetch"]

interface GitFetchInput {
  /** Remote name (default: origin) */
  remote?: string;
  /** Specific branch to fetch (default: all) */
  branch?: string;
  /** Fetch all remotes (default: false) */
  all?: boolean;
  /** Prune deleted branches (default: false) */
  prune?: boolean;
  /** Working directory */
  cwd?: string;
}

interface GitFetchOutput {
  remote: string;
  fetched: boolean;
}

git.clone

Clone a repository.

Path: ["git", "clone"]

interface GitCloneInput {
  /** Repository URL */
  url: string;
  /** Destination directory (default: derived from URL) */
  dest?: string;
  /** Branch to clone (default: default branch) */
  branch?: string;
  /** Create shallow clone with depth (default: full clone) */
  depth?: number;
  /** Working directory */
  cwd?: string;
}

interface GitCloneOutput {
  path: string;
  branch: string;
}

Example:

const clone = await client.exec(["git", "clone"], {
  url: "https://github.com/user/repo.git",
  dest: "./my-repo",
  depth: 1, // Shallow clone
});
console.log(`Cloned to ${clone.path}`);

git.remote

Get or set remote URLs.

Path: ["git", "remote"]

interface GitRemoteInput {
  /** Remote name (default: origin) */
  name?: string;
  /** New URL to set (if provided, sets the URL; otherwise gets it) */
  url?: string;
  /** Working directory */
  cwd?: string;
}

interface GitRemoteOutput {
  /** Remote name */
  name: string;
  /** Remote URL */
  url: string;
}

Predicate Procedures

These procedures return boolean predicates useful for conditional logic.

git.hasChanges

Check if there are any changes (unstaged, staged, or untracked).

Path: ["git", "hasChanges"]

interface GitPredicateInput {
  cwd?: string;
}

interface GitPredicateOutput {
  value: boolean;
}

git.hasStagedChanges

Check if there are staged changes ready to commit.

Path: ["git", "hasStagedChanges"]

git.hasUnstagedChanges

Check if there are unstaged changes.

Path: ["git", "hasUnstagedChanges"]

git.hasUntrackedFiles

Check if there are untracked files.

Path: ["git", "hasUntrackedFiles"]

git.hasLocalCommits

Check if there are local commits that haven't been pushed.

Path: ["git", "hasLocalCommits"]

git.isClean

Check if the working directory is clean (no changes).

Path: ["git", "isClean"]

Example:

const { value: hasChanges } = await client.exec(["git", "hasChanges"], {});
if (hasChanges) {
  console.log("Repository has uncommitted changes");
}

const { value: isClean } = await client.exec(["git", "isClean"], {});
if (isClean) {
  console.log("Working directory is clean");
}

Stash Procedures

git.stash.list

List all stashes.

Path: ["git", "stash", "list"]

interface GitStashEntry {
  /** Stash index (0 = most recent) */
  index: number;
  /** Stash reference (e.g., stash@{0}) */
  ref: string;
  /** Commit hash of the stash */
  hash: string;
  /** Stash message */
  message: string;
}

interface GitStashListOutput {
  /** List of stashes */
  stashes: GitStashEntry[];
  /** Total count */
  count: number;
}

git.stash.push

Save changes to stash.

Path: ["git", "stash", "push"]

interface GitStashPushInput {
  /** Stash message */
  message?: string;
  /** Include untracked files (default: false) */
  includeUntracked?: boolean;
  /** Keep staged changes in index (default: false) */
  keepIndex?: boolean;
  /** Specific paths to stash */
  paths?: string[];
  /** Working directory */
  cwd?: string;
}

interface GitStashPushOutput {
  /** Whether changes were stashed */
  stashed: boolean;
  /** Stash reference if created */
  ref?: string;
  /** Stash message */
  message?: string;
}

git.stash.pop

Apply and remove most recent stash.

Path: ["git", "stash", "pop"]

interface GitStashPopInput {
  /** Stash index to pop (default: 0 = most recent) */
  index?: number;
  cwd?: string;
}

interface GitStashPopOutput {
  /** Whether stash was applied */
  applied: boolean;
  /** Stash reference that was popped */
  ref: string;
  /** Whether stash was dropped (false if conflict) */
  dropped: boolean;
  /** Whether there was a merge conflict */
  conflict?: boolean;
}

git.stash.apply

Apply stash without removing it.

Path: ["git", "stash", "apply"]

interface GitStashApplyInput {
  /** Stash index to apply (default: 0 = most recent) */
  index?: number;
  cwd?: string;
}

interface GitStashApplyOutput {
  /** Whether stash was applied */
  applied: boolean;
  /** Stash reference that was applied */
  ref: string;
  /** Whether there was a merge conflict */
  conflict?: boolean;
}

git.stash.drop

Delete a stash.

Path: ["git", "stash", "drop"]

interface GitStashDropInput {
  /** Stash index to drop (default: 0 = most recent) */
  index?: number;
  cwd?: string;
}

interface GitStashDropOutput {
  /** Whether stash was dropped */
  dropped: boolean;
  /** Stash reference that was dropped */
  ref: string;
}

git.stash.export

Export stash as patch for snapshot storage.

Path: ["git", "stash", "export"]

interface GitStashExportInput {
  /** Stash index to export (default: 0 = most recent) */
  index?: number;
  cwd?: string;
}

interface GitStashExportOutput {
  /** Stash reference */
  ref: string;
  /** Commit hash */
  hash: string;
  /** Stash message */
  message: string;
  /** Patch content (diff) */
  patch: string;
  /** Whether stash includes untracked files */
  hasUntracked: boolean;
  /** Untracked files patch if present */
  untrackedPatch?: string;
}

git.stash.import

Import stash from patch.

Path: ["git", "stash", "import"]

interface GitStashImportInput {
  /** Patch content to apply and stash */
  patch: string;
  /** Message for the new stash */
  message?: string;
  /** Include untracked files (default: false) */
  includeUntracked?: boolean;
  cwd?: string;
}

interface GitStashImportOutput {
  /** Whether import succeeded */
  imported: boolean;
  /** New stash reference if created */
  ref?: string;
  /** Error message if failed */
  error?: string;
}

Stash Example:

// Save current work
await client.exec(["git", "stash", "push"], {
  message: "WIP: feature in progress",
  includeUntracked: true,
});

// List stashes
const stashes = await client.exec(["git", "stash", "list"], {});
console.log(`${stashes.count} stashes`);

// Apply most recent stash
await client.exec(["git", "stash", "apply"], { index: 0 });

// Export stash for backup
const exported = await client.exec(["git", "stash", "export"], { index: 0 });
fs.writeFileSync("backup.patch", exported.patch);

Complete Procedure List

| Category | Procedure | Description | |----------|-----------|-------------| | Repository State | git.status | Get repository status | | | git.log | Show commit history | | | git.diff | Show changes | | | git.branch | Branch operations | | | git.init | Initialize repository | | Staging & Commits | git.add | Stage files | | | git.commit | Create commit | | | git.checkout | Checkout branch/files | | Remote Operations | git.push | Push to remote | | | git.pull | Pull from remote | | | git.fetch | Fetch from remote | | | git.clone | Clone repository | | | git.remote | Manage remotes | | Predicates | git.hasChanges | Check for any changes | | | git.hasStagedChanges | Check for staged changes | | | git.hasUnstagedChanges | Check for unstaged changes | | | git.hasUntrackedFiles | Check for untracked files | | | git.hasLocalCommits | Check for unpushed commits | | | git.isClean | Check if working dir is clean | | Stash | git.stash.list | List stashes | | | git.stash.push | Save to stash | | | git.stash.pop | Apply and remove stash | | | git.stash.apply | Apply stash | | | git.stash.drop | Delete stash | | | git.stash.export | Export stash as patch | | | git.stash.import | Import stash from patch |

Usage Examples

Complete Git Workflow

// 1. Check status
const status = await client.exec(["git", "status"], {});
console.log(`On branch ${status.branch}`);

// 2. Stage changes
await client.exec(["git", "add"], { all: true });

// 3. Create commit
const commit = await client.exec(["git", "commit"], {
  message: "feat: implement new feature",
});

// 4. Push to remote
await client.exec(["git", "push"], {
  remote: "origin",
  setUpstream: true,
});

Conditional Operations with Predicates

const { value: hasChanges } = await client.exec(["git", "hasChanges"], {});
if (hasChanges) {
  await client.exec(["git", "stash", "push"], {
    message: "Auto-stash before pull",
  });
}

await client.exec(["git", "pull"], { rebase: true });

const { value: hasStashes } = await client.exec(["git", "stash", "list"], {});
if (hasStashes.count > 0) {
  await client.exec(["git", "stash", "pop"], {});
}

Branch Management

// Create feature branch
await client.exec(["git", "checkout"], {
  ref: "feature/new-feature",
  create: true,
});

// Work on feature...
await client.exec(["git", "add"], { all: true });
await client.exec(["git", "commit"], { message: "feat: complete feature" });

// Merge to main
await client.exec(["git", "checkout"], { ref: "main" });
await client.exec(["git", "pull"], {});

// Delete feature branch
await client.exec(["git", "branch"], {
  name: "feature/new-feature",
  delete: true,
});

Working with Remotes

// Clone repository
const clone = await client.exec(["git", "clone"], {
  url: "https://github.com/user/repo.git",
  dest: "./my-repo",
});

// Set remote
await client.exec(["git", "remote"], {
  name: "upstream",
  url: "https://github.com/original/repo.git",
});

// Fetch from upstream
await client.exec(["git", "fetch"], {
  remote: "upstream",
  prune: true,
});

Dependencies

graph LR
    A[@procedures/git] --> B[@procedures/client]
    A --> C[zod]

    B -.->|provides| D[Client & Procedure System]
    C -.->|provides| E[Schema Validation]

    style A fill:#e1f5ff
    style B fill:#ffe1e1
    style C fill:#fff4e1
  • @procedures/client - Core client library providing the procedure system
  • zod - Schema validation library

Integration with Ecosystem

Used By

// bundle-dev/src/register.ts
import "@procedures/git"; // registers its procedures

// client-lib uses git procedures internally
import { gitStatus, gitAdd, gitCommit } from "@procedures/git";

Usage in client-lib

The client-lib package uses git procedures extensively for package management:

// From client-lib/src/git/operations.ts
export async function stageAll(cwd: string, ctx: ProcedureContext): Promise<void> {
  await ctx.client.call(["git", "add"], { all: true, cwd });
}

export async function commit(
  message: string,
  cwd: string,
  ctx: ProcedureContext
): Promise<string> {
  const result = await ctx.client.call(["git", "commit"], { message, cwd });
  return result.hash;
}

Configuration

Auto-Registration

Procedures are automatically registered when the package is loaded:

{
  "client": {
    "procedures": "./dist/register.js"
  },
  "scripts": {
    "postinstall": "client-registry announce"
  }
}

Build Scripts

# Build the package
pnpm build

# Type check
pnpm typecheck

# Clean build artifacts
pnpm clean

Advanced Usage

Direct Function Imports

The handlers start git themselves. Their context is optional: it gives only the abort signal.

import { gitStatus, gitCommit, gitPush } from "@procedures/git";

const status = await gitStatus({ cwd: "./repo" });
await gitCommit({ message: "feat: new feature", cwd: "./repo" });
await gitPush({ remote: "origin", cwd: "./repo" }, { signal: AbortSignal.timeout(60_000) });

Custom Registration

import { createProcedure, registerProcedures } from "@procedures/client";
import { gitStatus, GitStatusInputSchema } from "@procedures/git";

// Register under custom namespace
const customStatusProcedure = createProcedure()
  .path(["custom", "git", "status"])
  .input(GitStatusInputSchema)
  .handler(gitStatus)
  .build();

registerProcedures([customStatusProcedure]);

Error Handling

All git procedures may throw errors for invalid operations:

try {
  await client.exec(["git", "commit"], {
    message: "feat: new feature",
  });
} catch (error) {
  if (error.message.includes("nothing to commit")) {
    console.log("No changes to commit");
  } else {
    throw error;
  }
}

Performance Considerations

  • Command Execution: Each procedure spawns a git process
  • Large Repositories: Operations may be slower on repos with extensive history
  • Batch Operations: Use predicate checks to avoid unnecessary operations
  • Stash Export/Import: Useful for snapshotting state without git history overhead

Troubleshooting

Git Not Found

Ensure git is installed and in PATH:

git --version

Permission Errors

Check repository permissions:

ls -la .git

Detached HEAD State

Use git.checkout to return to a branch:

await client.exec(["git", "checkout"], { ref: "main" });

License

MIT

Related Packages