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

git-workspace-service

v0.4.3

Published

Git workspace provisioning and credential management service with OAuth device flow authentication

Downloads

3,664

Readme

git-workspace-service

Git workspace provisioning and credential management service. Handles cloning repositories, managing branches, credentials, and PR creation.

Features

  • Workspace provisioning - Clone repos, create branches, configure git
  • Git Worktrees - Fast parallel workspaces with shared .git directory
  • Credential management - Secure credential handling with TTL and revocation
  • Multiple providers - Support for GitHub, GitLab, Bitbucket, Azure DevOps
  • GitHub App support - First-class GitHub App authentication
  • OAuth Device Flow - Interactive authentication for CLI/agents (RFC 8628)
  • Token caching - Encrypted persistent storage for OAuth tokens
  • User credentials - Support for PAT, OAuth tokens, and SSH
  • Fine-grained permissions - Control what agents can do
  • Branch naming - Automatic branch naming with execution context
  • PR & Issue management - Create PRs, manage issues, add comments
  • Event system - Subscribe to workspace lifecycle events
  • TypeScript-first - Full type definitions included

Installation

npm install git-workspace-service
# or
pnpm add git-workspace-service

For GitHub App support, also install:

npm install @octokit/rest @octokit/auth-app

Quick Start

import {
  WorkspaceService,
  CredentialService,
  GitHubProvider,
} from 'git-workspace-service';

// Set up credential service with GitHub provider
const githubProvider = new GitHubProvider({
  appId: process.env.GITHUB_APP_ID!,
  privateKey: process.env.GITHUB_PRIVATE_KEY!,
});
await githubProvider.initialize();

const credentialService = new CredentialService({
  defaultTtlSeconds: 3600,
  maxTtlSeconds: 7200,
});
credentialService.registerProvider(githubProvider);

// Create workspace service
const workspaceService = new WorkspaceService({
  config: {
    baseDir: '/tmp/workspaces',
  },
  credentialService,
});
await workspaceService.initialize();

// Provision a workspace
const workspace = await workspaceService.provision({
  repo: 'https://github.com/owner/repo',
  branchStrategy: 'feature_branch',
  baseBranch: 'main',
  execution: {
    id: 'exec-123',
    patternName: 'code-review',
  },
  task: {
    id: 'task-456',
    role: 'engineer',
    slug: 'auth-feature',
  },
});

console.log(`Workspace created at: ${workspace.path}`);
console.log(`Branch: ${workspace.branch.name}`);

// Do work in the workspace...

// Finalize with PR
const pr = await workspaceService.finalize(workspace.id, {
  push: true,
  createPr: true,
  pr: {
    title: 'Add authentication feature',
    body: 'Implements OAuth login',
    targetBranch: 'main',
    labels: ['enhancement'],
  },
  cleanup: true,
});

console.log(`PR created: ${pr?.url}`);

User-Provided Credentials

Instead of using GitHub App, users can provide their own PAT or OAuth token:

const workspace = await workspaceService.provision({
  repo: 'https://github.com/owner/repo',
  branchStrategy: 'feature_branch',
  baseBranch: 'main',
  execution: {
    id: 'exec-123',
    patternName: 'my-pattern',
  },
  task: {
    id: 'task-456',
    role: 'engineer',
  },
  userCredentials: {
    type: 'pat',
    token: 'ghp_xxxx...',
  },
});

Or use SSH authentication (relies on system SSH agent):

const workspace = await workspaceService.provision({
  repo: '[email protected]:owner/repo.git',
  // ...
  userCredentials: {
    type: 'ssh',
  },
});

OAuth Device Flow

For CLI applications and AI agents, use the OAuth Device Code Flow for interactive authentication:

import {
  CredentialService,
  OAuthDeviceFlow,
  FileTokenStore,
  DEFAULT_AGENT_PERMISSIONS,
} from 'git-workspace-service';

// Set up token store for persistent caching (with encryption)
const tokenStore = new FileTokenStore({
  directory: '~/.myapp/tokens',
  encryptionKey: process.env.TOKEN_ENCRYPTION_KEY,
});

// Create credential service with OAuth support
const credentialService = new CredentialService({
  tokenStore,
  oauth: {
    clientId: process.env.GITHUB_CLIENT_ID!,
    permissions: DEFAULT_AGENT_PERMISSIONS,
    promptEmitter: {
      onAuthRequired(prompt) {
        console.log(`Visit: ${prompt.verificationUri}`);
        console.log(`Enter code: ${prompt.userCode}`);
      },
      onAuthComplete(result) {
        if (result.success) {
          console.log('Authenticated!');
        }
      },
    },
  },
});

// Request credentials - will use cached token or trigger device flow
const credential = await credentialService.getCredentials({
  repo: 'https://github.com/owner/repo',
  access: 'write',
  context: {
    executionId: 'exec-123',
    taskId: 'task-456',
    reason: 'Code review',
  },
});

Credential Priority

When requesting credentials, the service checks sources in this order:

  1. User-provided - PAT, OAuth token, or SSH credentials passed directly
  2. Cached OAuth - Valid token from TokenStore (with auto-refresh)
  3. Provider - GitHub App or other registered provider
  4. OAuth Device Flow - Interactive authentication (if configured)

Fine-Grained Permissions

Control what agents can do with AgentPermissions:

import { DEFAULT_AGENT_PERMISSIONS } from 'git-workspace-service';

const permissions = {
  repositories: { type: 'selected', repos: ['owner/repo'] },
  contents: 'write',      // 'none' | 'read' | 'write'
  pullRequests: 'write',
  issues: 'read',
  metadata: 'read',
  canDeleteBranch: true,
  canForcePush: false,    // Dangerous operations off by default
  canDeleteRepository: false,
  canAdminister: false,
};

Git Worktrees

For parallel work on the same repository, use worktrees instead of clones. Worktrees share the .git directory, making them faster to create and using less disk space.

┌──────────────────────────┬────────────────────────────────────────┐
│          Clone           │                Worktree                │
├──────────────────────────┼────────────────────────────────────────┤
│ Full .git copy each time │ Shared .git directory                  │
│ Slower for same repo     │ Fast - just checkout                   │
│ More disk space          │ Minimal disk                           │
│ Good for different repos │ Perfect for parallel work on SAME repo │
└──────────────────────────┴────────────────────────────────────────┘

Creating a Worktree

// First, create a clone workspace (the parent)
const parent = await workspaceService.provision({
  repo: 'https://github.com/owner/repo',
  strategy: 'clone',  // explicit, but this is the default
  branchStrategy: 'feature_branch',
  baseBranch: 'main',
  execution: { id: 'exec-123', patternName: 'review' },
  task: { id: 'task-1', role: 'architect' },
});

// Then create worktrees from it for parallel agents
const reviewerWorkspace = await workspaceService.provision({
  repo: 'https://github.com/owner/repo',
  strategy: 'worktree',
  parentWorkspace: parent.id,  // Required for worktrees
  branchStrategy: 'feature_branch',
  baseBranch: 'main',
  execution: { id: 'exec-123', patternName: 'review' },
  task: { id: 'task-2', role: 'reviewer' },
});

// Or use the convenience method
const testerWorkspace = await workspaceService.addWorktree(parent.id, {
  branch: 'main',
  execution: { id: 'exec-123', patternName: 'review' },
  task: { id: 'task-3', role: 'tester' },
});

Managing Worktrees

// List all worktrees for a parent
const worktrees = workspaceService.listWorktrees(parent.id);

// Remove a specific worktree
await workspaceService.removeWorktree(reviewerWorkspace.id);

// Cleanup parent also cleans up all its worktrees
await workspaceService.cleanup(parent.id);

Worktree Benefits for Multi-Agent Systems

  • Shared credentials: Worktrees reuse the parent's credential
  • Faster provisioning: No network clone, just local checkout
  • Less disk space: Single .git directory shared across all worktrees
  • Isolated branches: Each worktree can work on a different branch

Event System

Subscribe to workspace lifecycle events:

const unsubscribe = workspaceService.onEvent((event) => {
  switch (event.type) {
    case 'workspace:provisioning':
      console.log('Provisioning workspace...');
      break;
    case 'workspace:ready':
      console.log('Workspace ready!');
      break;
    case 'credential:granted':
      console.log(`Credential ${event.credentialId} granted`);
      break;
    case 'pr:created':
      console.log(`PR created: ${event.data?.prUrl}`);
      break;
    case 'workspace:cleaned_up':
      console.log('Workspace cleaned up');
      break;
  }
});

// Later, unsubscribe
unsubscribe();

Branch Naming

Automatic branch naming with customizable prefix:

import { generateBranchName, parseBranchName } from 'git-workspace-service';

// Generate branch name
const branchName = generateBranchName({
  executionId: 'exec-123',
  role: 'engineer',
  slug: 'auth-feature',
  baseBranch: 'main',
});
// Result: 'parallax/exec-123/engineer-auth-feature'

// Parse branch name
const parsed = parseBranchName('parallax/exec-123/engineer-auth-feature');
// Result: { executionId: 'exec-123', role: 'engineer', slug: 'auth-feature' }

Custom Branch Names

If you need a specific branch name (e.g. for deterministic naming or external conventions), pass branchName in the workspace config to bypass auto-generation:

const workspace = await workspaceService.provision({
  repo: 'https://github.com/owner/repo',
  branchStrategy: 'feature_branch',
  baseBranch: 'main',
  branchName: 'test/claude-nonce-abc123',  // Used verbatim
  execution: { id: 'exec-123', patternName: 'review' },
  task: { id: 'task-456', role: 'engineer' },
});

console.log(workspace.branch.name); // 'test/claude-nonce-abc123'

When branchName is set, isManagedBranch() may return false for the resulting branch — this is expected since custom names intentionally bypass the naming convention. The branch is still created from baseBranch and works with both clone and worktree strategies.

API Reference

WorkspaceService

class WorkspaceService {
  constructor(options: WorkspaceServiceOptions);

  // Initialize the service
  initialize(): Promise<void>;

  // Provision a new workspace
  provision(config: WorkspaceConfig): Promise<Workspace>;

  // Finalize workspace (push, create PR, cleanup)
  finalize(workspaceId: string, options: WorkspaceFinalization): Promise<PullRequestInfo | void>;

  // Get workspace by ID
  get(workspaceId: string): Workspace | null;

  // Get all workspaces for an execution
  getForExecution(executionId: string): Workspace[];

  // Clean up a workspace
  cleanup(workspaceId: string): Promise<void>;

  // Clean up all workspaces for an execution
  cleanupForExecution(executionId: string): Promise<void>;

  // Subscribe to events
  onEvent(handler: WorkspaceEventHandler): () => void;
}

CredentialService

class CredentialService {
  constructor(options?: CredentialServiceOptions);

  // Register a provider adapter
  registerProvider(provider: GitProviderAdapter): void;

  // Get credentials for a repository
  getCredentials(request: GitCredentialRequest): Promise<GitCredential>;

  // Revoke a credential
  revokeCredential(grantId: string): Promise<void>;

  // Revoke all credentials for an execution
  revokeForExecution(executionId: string): Promise<number>;

  // Check if a credential is valid
  isValid(grantId: string): boolean;

  // Get grant info
  getGrant(grantId: string): Promise<CredentialGrant | null>;

  // Get all grants for an execution
  getGrantsForExecution(executionId: string): Promise<CredentialGrant[]>;
}

GitHubProvider

class GitHubProvider implements GitProviderAdapter {
  constructor(config: GitHubProviderConfig, logger?: GitHubProviderLogger);

  // Initialize and fetch installations
  initialize(): Promise<void>;

  // Register an installation
  registerInstallation(installationId: number): Promise<GitHubAppInstallation>;

  // Get credentials for a repo
  getCredentialsForRepo(owner: string, repo: string, access: 'read' | 'write', ttlSeconds?: number): Promise<GitCredential>;

  // Create a PR
  createPullRequestForRepo(owner: string, repo: string, options: { title, body, head, base, draft?, labels?, reviewers? }): Promise<PullRequestInfo>;

  // Delete a branch
  deleteBranch(owner: string, repo: string, branch: string): Promise<void>;

  // List managed branches
  listManagedBranches(owner: string, repo: string, prefix?: string): Promise<string[]>;
}

OAuthDeviceFlow

class OAuthDeviceFlow {
  constructor(config: OAuthDeviceFlowConfig, logger?: OAuthDeviceFlowLogger);

  // Start device flow and wait for authorization
  authorize(): Promise<OAuthToken>;

  // Request a device code (step 1)
  requestDeviceCode(): Promise<DeviceCodeResponse>;

  // Poll for token (step 2)
  pollForToken(deviceCode: DeviceCodeResponse): Promise<OAuthToken>;

  // Refresh an expired token
  refreshToken(refreshToken: string): Promise<OAuthToken>;
}

TokenStore

// Abstract base class
abstract class TokenStore {
  save(provider: GitProvider, token: OAuthToken): Promise<void>;
  get(provider: GitProvider): Promise<OAuthToken | null>;
  clear(provider?: GitProvider): Promise<void>;
  list(): Promise<GitProvider[]>;
  isExpired(token: OAuthToken): boolean;
  needsRefresh(token: OAuthToken): boolean;
}

// In-memory store (for testing)
class MemoryTokenStore extends TokenStore { }

// File-based store with encryption
class FileTokenStore extends TokenStore {
  constructor(options?: {
    directory?: string;      // Default: ~/.parallax/tokens
    encryptionKey?: string;  // AES-256-CBC encryption
  });
}

Event Types

| Event | Description | |-------|-------------| | workspace:provisioning | Workspace provisioning started | | workspace:ready | Workspace is ready for use | | workspace:error | Workspace provisioning failed | | workspace:finalizing | Workspace finalization started | | workspace:cleaned_up | Workspace has been cleaned up | | worktree:added | Git worktree added to parent | | worktree:removed | Git worktree removed | | credential:granted | Credential was granted | | credential:revoked | Credential was revoked | | pr:created | Pull request was created | | pr:merged | Pull request was merged |

License

MIT