envpact — The Complete Engineering Specification for a Centralized, Serverless Secrets Manager for Solo Developers

A comprehensive engineering specification and vision document for envpact: a $0, serverless, Git-backed secrets management ecosystem with a Node.js CLI, Python module, MCP server, VS Code extension, GitHub Action, and web dashboard — all designed for solo developers managing 100+ public repositories.

envpact — The Complete Engineering Specification for a

Centralized, Serverless Secrets Manager for Solo Developers

envpact is a centralized, developer-friendly, and completely free secrets management ecosystem designed specifically for solo developers managing 100+ public GitHub repositories.

The name "envpact" is a portmanteau of env (environment) and pact (a formal agreement or contract). It represents a binding contract between you and your secrets: a single source of truth that governs how environment variables are stored, shared, resolved, and deployed across your entire development infrastructure.

This document serves two purposes:

  1. A published engineering vision and architecture overview.
  2. A comprehensive specification detailed enough for any AI coding agent (Cloud Code, Cursor, Windsurf, Claude Code) to implement the entire ecosystem from scratch.

The Problem

Solo developers face a unique, compounding secrets management dilemma that no existing tool fully solves.

1. Public Repositories Cannot Contain Plaintext Secrets

If you maintain 100+ public GitHub repositories (as most portfolio-building, open-source-contributing solo developers do), you cannot commit .env files. A single accidental push of a plaintext API key to a public repository exposes it to automated bots that scrape GitHub in real time. GitHub's secret scanning catches some patterns, but the damage is already done by the time you receive the alert.

2. Shared Secrets Are Duplicated Across Dozens of Projects

You reuse the same credentials everywhere. Your Cloudflare API token, OpenAI API key, Resend API key, database connection strings, Stripe keys — these appear in 30, 40, sometimes 60+ projects. When you store these per-repository (whether encrypted or not), you are violating the DRY principle at the infrastructure level.

3. Key Rotation Is a Manual Nightmare

If your OpenAI API key is compromised or expires, you must rotate it in every single project that uses it. With per-repository encryption (dotenvx, SOPS, git-crypt), this means navigating to each project directory, decrypting, updating, re-encrypting, committing, and pushing. For 40 repositories using the same key, that is 200+ manual steps.

4. Existing Tools Hit Limits or Cost Money

ToolFree Tier LimitCost to Scale
Doppler10 projects$252/year
Infisical Cloud3 projects$216+/year
dotenvx / SOPSUnlimited (but per-repo)$0 (DRY violation)
GitHub SecretsUnlimited (CI/CD only)$0 (no local .env)
1Password CLIUnlimited$36/year
Bitwarden Secrets Mgr3 projects$72/year

5. AI Coding Agents Need Local .env Files

Modern AI coding agents (Cursor, Windsurf, Claude Code, Cline) generate entire projects in minutes. They scaffold .env.example files, write code that references environment variables, and need those variables to actually be present on disk to test and run the code. There is no "pull from Doppler" integration in most AI agents. They just read .env files from the working directory.


The Solution: envpact

envpact solves all of these problems using a $0 serverless architecture built on infrastructure every developer already has: Git and GitHub.

Core Principles

  1. Single Source of Truth: One private GitHub repository ({username}/envpact-secrets) containing a single secrets.json file stores every secret you own.
  2. Shared Secret References: Project-specific secrets can reference shared secrets using a shared.KEY_NAME syntax, enabling DRY key management.
  3. Dry Rotation: Update a shared secret in one line, push it, and every project that references it automatically gets the new value on next resolution.
  4. Zero Infrastructure: No servers, no databases, no VPS, no Docker containers. Just Git repositories.
  5. AI-Agent Ready: Local .env files are generated on disk (and gitignored), allowing any AI agent to work seamlessly.
  6. Multi-Runtime: Available as Node.js CLI, Python module, MCP server, VS Code extension, GitHub Action, and web dashboard — use whichever fits your workflow.

Architecture Overview

┌──────────────────────────────────────────────────────┐
│  {username}/envpact-secrets (PRIVATE GitHub repo)    │
│  └── secrets.json  ← single source of truth          │
│       ├── shared: { OPENAI_API_KEY: "sk-..." }       │
│       └── projects: { "my-app": { ... } }            │
└─────────────────────┬────────────────────────────────┘
                      │ git clone/pull (SSH or HTTPS)
                      │
    ┌─────────────────┼─────────────────────────┐
    │                 │                         │
    ▼                 ▼                         ▼
┌─────────┐   ┌──────────────┐   ┌──────────────────┐
│envpact  │   │ envpact-mcp  │   │ envpact (Python) │
│  -cli   │   │ (MCP Server) │   │  (PyPI module)   │
│(npm CLI)│   │  (npm pkg)   │   │                  │
└────┬────┘   └──────┬───────┘   └────────┬─────────┘
     │               │                    │
     ▼               ▼                    ▼
┌─────────┐   ┌──────────────┐   ┌──────────────────┐
│ .env    │   │ AI Agents    │   │ Python Scripts   │
│ (local) │   │ (Cursor,     │   │ & Automation     │
│         │   │  Windsurf,   │   │                  │
│         │   │  Claude)     │   │                  │
└────┬────┘   └──────────────┘   └──────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────┐
│ envpact-action (GitHub Action)                       │
│  └── Resolves secrets → gh secret set               │
│       → Syncs to GitHub Actions Secrets              │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────┐
│ envpact-vscode (VS Code Extension)                   │
│  └── GUI for managing .env files in VS Code          │
│       → Secret resolution, validation, creation      │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────┐
│ envpact-dashboard (Web Dashboard)                    │
│  └── Hosted on Cloudflare Pages                      │
│       → Visual UI for managing secrets.json          │
│       → No server-side storage (reads from Git)      │
└──────────────────────────────────────────────────────┘

Ecosystem Components

The envpact ecosystem consists of 8 repositories, all under the GitHub user chirag127:

RepositoryPackageTypeDescription
envpactUmbrella repoParent monorepo linking all components via Git submodules
envpact-clienvpact-cli (npm)Node.js CLIZero-dependency CLI for local .env generation and GitHub Actions sync
envpact-mcpenvpact-mcp (npm)MCP ServerModel Context Protocol server for AI coding agents
envpact (PyPI)envpact (PyPI)Python modulePython SDK for programmatic secret resolution
envpact-actionchirag127/envpact-actionGitHub ActionGitHub Actions marketplace action for CI/CD secret sync
envpact-vscodeenvpact (VS Code)VS Code ExtensionGUI-based secret management inside VS Code
envpact-dashboardWeb AppCloudflare Pages-hosted dashboard for visual secret management
envpact-secretsPrivate repoUser's private secrets vault (auto-created by CLI)

Component 1: The Secrets Vault (envpact-secrets)

Repository Structure

envpact-secrets/           # PRIVATE GitHub repository
├── secrets.json           # The single source of truth
├── .gitignore
└── README.md

Schema: secrets.json

{
  "$schema": "https://envpact.dev/schema/v1.json",
  "version": 1,
  "shared": {
    "CLOUDFLARE_API_TOKEN": "cf_abc123...",
    "CLOUDFLARE_ACCOUNT_ID": "a1b2c3d4e5...",
    "OPENAI_API_KEY": "sk-proj-...",
    "RESEND_API_KEY": "re_...",
    "GITHUB_TOKEN": "ghp_...",
    "DATABASE_URL": "postgresql://user:pass@host/db",
    "STRIPE_SECRET_KEY": "sk_live_...",
    "STRIPE_PUBLISHABLE_KEY": "pk_live_..."
  },
  "projects": {
    "blog.oriz.in": {
      "CLOUDFLARE_API_TOKEN": "shared.CLOUDFLARE_API_TOKEN",
      "SITE_URL": "https://blog.oriz.in",
      "PUBLIC_CF_BEACON": "abc123def456",
      "RESEND_API_KEY": "shared.RESEND_API_KEY"
    },
    "my-saas-app": {
      "OPENAI_API_KEY": "shared.OPENAI_API_KEY",
      "STRIPE_SECRET_KEY": "shared.STRIPE_SECRET_KEY",
      "DATABASE_URL": "shared.DATABASE_URL",
      "PORT": "3000",
      "NODE_ENV": "production"
    },
    "discord-bot": {
      "DISCORD_BOT_TOKEN": "MTk...",
      "OPENAI_API_KEY": "shared.OPENAI_API_KEY",
      "LOG_LEVEL": "info"
    }
  },
  "metadata": {
    "created_at": "2026-06-15T00:00:00Z",
    "updated_at": "2026-06-15T00:00:00Z",
    "owner": "chirag127"
  }
}

Resolution Rules

  1. If a project value starts with shared., resolve it by looking up the key after the dot in the shared object.
  2. If the referenced shared key does not exist, prompt the user to provide a value and save it to shared.
  3. If a project key is missing entirely from the project config but exists in .env.example, prompt the user.
  4. All resolved values are written to the local .env file as plaintext (gitignored).

Auto-Creation

When a user runs npx envpact-cli --init for the first time and does not have a vault:

  1. The CLI checks if ~/.envpact/secrets/ exists.
  2. If not, it asks for the Git URL of the private repo.
  3. If the user does not have a repo, the CLI offers to create one automatically using the GitHub CLI (gh):
    gh repo create envpact-secrets --private --clone
    
  4. It initializes secrets.json with the default schema.
  5. It commits and pushes the initial file.

Component 2: envpact-cli (Node.js CLI)

Package Details

  • NPM Package: envpact-cli
  • Binary Name: envpact
  • Language: Node.js (CommonJS, zero dependencies)
  • Min Node Version: 18.0.0
  • License: MIT

Installation

# Run directly (no install needed)
npx envpact-cli

# Or install globally
npm install -g envpact-cli

CLI Commands and Flags

envpact [options]

Options:
  --init <git-url>    Initialize envpact with a private
                      secrets repository URL
  --github, -g        Sync resolved secrets to GitHub
                      Actions repository secrets
  --project <name>    Override auto-detected project name
  --env-file <path>   Path to .env.example
                      (default: .env.example)
  --output <path>     Path to output .env file
                      (default: .env)
  --dry-run           Print resolved env without writing
  --rotate <key>      Rotate a shared secret interactively
  --list              List all projects in the vault
  --list-shared       List all shared secrets (names only,
                      values masked)
  --add <key>=<value> Add a secret to the current project
  --add-shared <k>=<v> Add a shared secret
  --version, -v       Print version
  --help, -h          Show help

Core Logic (Pseudocode)

#!/usr/bin/env node

// 1. Parse CLI arguments
// 2. Determine home directory and config paths
//    HOME = process.env.USERPROFILE
//      || process.env.HOME
//      || process.env.HOMEPATH
//    CONFIG_DIR = path.join(HOME, '.envpact')
//    SECRETS_DIR = path.join(CONFIG_DIR, 'secrets')
//    SECRETS_FILE = path.join(SECRETS_DIR,
//      'secrets.json')

// 3. If --init flag, clone the private repo
//    into SECRETS_DIR

// 4. If SECRETS_DIR exists, git pull to sync

// 5. Parse secrets.json

// 6. Auto-detect project name from:
//    a. git remote origin URL → extract repo name
//    b. Fallback: basename of current directory

// 7. Parse .env.example to get required keys

// 8. For each required key:
//    a. Check if project config has this key
//    b. If value starts with "shared.",
//       resolve from shared
//    c. If value is missing, prompt user
//       interactively
//    d. Save new values back to secrets.json

// 9. Write resolved key=value pairs to .env

// 10. If secrets.json was modified:
//     a. Write updated JSON to disk
//     b. git add, commit, push in SECRETS_DIR

// 11. If --github flag:
//     a. Verify gh CLI is installed
//     b. For each resolved secret:
//        gh secret set KEY --body "VALUE"

Project Detection Algorithm

function getProjectName() {
  // Try git remote first
  const gitUrl = execSync(
    'git config --get remote.origin.url'
  ).toString().trim();

  // Extract repo name from SSH or HTTPS URL
  // [email protected]:user/repo.git → "repo"
  // https://github.com/user/repo.git → "repo"
  const match = gitUrl.match(
    /[:/]([^/]+)\/([^/]+?)(?:\.git)?$/
  );
  if (match) return match[2];

  // Fallback: directory name
  return path.basename(process.cwd());
}

Repository Structure

envpact-cli/
├── bin/
│   └── envpact.js       # Main CLI entry point
├── lib/
│   ├── config.js        # Path constants, defaults
│   ├── git.js           # Git operations (clone, pull,
│   │                    #   commit, push)
│   ├── parser.js        # .env.example parser
│   ├── resolver.js      # Secret resolution engine
│   ├── github.js        # GitHub CLI integration
│   └── prompt.js        # Interactive prompts (readline)
├── tests/
│   ├── resolver.test.js
│   ├── parser.test.js
│   └── github.test.js
├── package.json
├── LICENSE              # MIT
├── README.md
├── AGENTS.md
├── .github/
│   └── workflows/
│       ├── ci.yml       # Lint + test on push
│       └── publish.yml  # npm publish on tag
└── .gitignore

package.json

{
  "name": "envpact-cli",
  "version": "1.0.0",
  "description": "Zero-dependency CLI to sync environment variables from a centralized private Git repository",
  "main": "bin/envpact.js",
  "bin": {
    "envpact": "./bin/envpact.js"
  },
  "scripts": {
    "test": "node --test tests/",
    "lint": "biome check .",
    "format": "biome format --write ."
  },
  "keywords": [
    "secrets",
    "env",
    "dotenv",
    "git",
    "cli",
    "secrets-manager",
    "environment-variables",
    "envpact"
  ],
  "author": "Chirag Singhal <[email protected]>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chirag127/envpact-cli.git"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

Component 3: envpact-mcp (MCP Server)

Package Details

  • NPM Package: envpact-mcp
  • Protocol: Model Context Protocol (MCP) over stdio
  • Language: Node.js (ESM)
  • License: MIT

MCP Configuration

Add to your AI agent's MCP settings (Cursor, Claude Desktop, Windsurf):

{
  "mcpServers": {
    "envpact": {
      "command": "npx",
      "args": ["-y", "envpact-mcp"]
    }
  }
}

MCP Tools Specification

The MCP server exposes the following tools:

Tool 1: generate_env

Generates a .env file for the current project by resolving secrets from the central vault.

{
  "name": "generate_env",
  "description": "Generate a .env file for the current project by resolving secrets from the envpact vault. Reads .env.example, resolves shared references, and writes .env.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "project_name": {
        "type": "string",
        "description": "Project name to resolve secrets for. Auto-detected from git remote if omitted."
      },
      "working_directory": {
        "type": "string",
        "description": "Path to the project directory containing .env.example. Defaults to cwd."
      }
    },
    "required": []
  }
}

Tool 2: list_projects

Lists all projects configured in the secrets vault.

{
  "name": "list_projects",
  "description": "List all projects configured in the envpact secrets vault.",
  "inputSchema": {
    "type": "object",
    "properties": {},
    "required": []
  }
}

Tool 3: list_shared

Lists all shared secret names (values are masked).

{
  "name": "list_shared",
  "description": "List all shared secret names from the envpact vault. Values are masked for security (only names are returned).",
  "inputSchema": {
    "type": "object",
    "properties": {},
    "required": []
  }
}

Tool 4: add_secret

Adds or updates a secret for a specific project.

{
  "name": "add_secret",
  "description": "Add or update a secret for a specific project in the envpact vault.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "project_name": {
        "type": "string",
        "description": "The project to add the secret to."
      },
      "key": {
        "type": "string",
        "description": "The environment variable name."
      },
      "value": {
        "type": "string",
        "description": "The value. Use 'shared.KEY_NAME' to reference a shared secret."
      }
    },
    "required": ["project_name", "key", "value"]
  }
}

Tool 5: add_shared_secret

Adds or updates a shared secret in the vault.

{
  "name": "add_shared_secret",
  "description": "Add or update a shared secret in the envpact vault. Shared secrets can be referenced by any project using the 'shared.KEY_NAME' syntax.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "key": {
        "type": "string",
        "description": "The shared secret name."
      },
      "value": {
        "type": "string",
        "description": "The secret value."
      }
    },
    "required": ["key", "value"]
  }
}

Tool 6: rotate_secret

Rotates a shared secret and updates the vault.

{
  "name": "rotate_secret",
  "description": "Rotate a shared secret. Updates the value in the vault and optionally syncs to GitHub Actions for all projects that reference it.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "key": {
        "type": "string",
        "description": "The shared secret name to rotate."
      },
      "new_value": {
        "type": "string",
        "description": "The new secret value."
      },
      "sync_github": {
        "type": "boolean",
        "description": "If true, sync the rotated secret to GitHub Actions for all referencing projects.",
        "default": false
      }
    },
    "required": ["key", "new_value"]
  }
}

Tool 7: sync_github

Syncs resolved secrets to GitHub Actions repository secrets.

{
  "name": "sync_github",
  "description": "Sync all resolved secrets for a project to GitHub Actions repository secrets using the GitHub CLI.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "project_name": {
        "type": "string",
        "description": "The project to sync. Auto-detected from git remote if omitted."
      }
    },
    "required": []
  }
}

Repository Structure

envpact-mcp/
├── src/
│   ├── index.js         # MCP server entry point
│   ├── tools/
│   │   ├── generate-env.js
│   │   ├── list-projects.js
│   │   ├── list-shared.js
│   │   ├── add-secret.js
│   │   ├── add-shared-secret.js
│   │   ├── rotate-secret.js
│   │   └── sync-github.js
│   └── lib/
│       ├── vault.js     # Shared vault operations
│       └── resolver.js  # Secret resolution engine
├── tests/
│   ├── tools.test.js
│   └── vault.test.js
├── package.json
├── LICENSE
├── README.md
├── AGENTS.md
└── .github/
    └── workflows/
        ├── ci.yml
        └── publish.yml

package.json

{
  "name": "envpact-mcp",
  "version": "1.0.0",
  "type": "module",
  "description": "MCP server for envpact — centralized secrets management for AI coding agents",
  "main": "src/index.js",
  "bin": {
    "envpact-mcp": "./src/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "latest"
  },
  "keywords": [
    "mcp",
    "secrets",
    "env",
    "ai-agents",
    "envpact"
  ],
  "author": "Chirag Singhal <[email protected]>",
  "license": "MIT"
}

Component 4: envpact (Python Module)

Package Details

  • PyPI Package: envpact
  • Language: Python 3.10+
  • Dependencies: Zero runtime dependencies (only stdlib: json, subprocess, pathlib, os)
  • License: MIT

Installation

# Install from PyPI
pip install envpact

# Or use directly
python -m envpact

Python API

from envpact import EnvPact

# Initialize with vault path
pact = EnvPact(
    vault_path="~/.envpact/secrets",
    project_name="my-app"  # auto-detected if omitted
)

# Resolve all secrets for current project
secrets = pact.resolve()
# Returns: {"OPENAI_API_KEY": "sk-...", "PORT": "3000"}

# Generate .env file
pact.generate_env(output_path=".env")

# List all projects
projects = pact.list_projects()
# Returns: ["blog.oriz.in", "my-saas-app", ...]

# List shared secrets (names only)
shared = pact.list_shared()
# Returns: ["OPENAI_API_KEY", "CLOUDFLARE_API_TOKEN", ...]

# Add a secret
pact.add_secret("my-app", "NEW_KEY", "new_value")

# Add a shared secret
pact.add_shared("SENDGRID_API_KEY", "SG.xxx")

# Rotate a shared secret
pact.rotate("OPENAI_API_KEY", "sk-new-key-here")

# Sync to GitHub Actions
pact.sync_github(project_name="my-app")

# Pull latest vault changes
pact.pull()

CLI Interface

# Generate .env for current project
python -m envpact

# Initialize vault
python -m envpact --init [email protected]:user/secrets.git

# Sync to GitHub
python -m envpact --github

# List projects
python -m envpact --list

# Rotate a key
python -m envpact --rotate OPENAI_API_KEY

# Dry run
python -m envpact --dry-run

Repository Structure

envpact/                    # Python package repo
├── src/
│   └── envpact/
│       ├── __init__.py     # Public API exports
│       ├── __main__.py     # CLI entry point
│       ├── cli.py          # Argument parsing
│       ├── vault.py        # Vault operations
│       ├── resolver.py     # Secret resolution
│       ├── git.py          # Git operations
│       ├── github.py       # GitHub CLI integration
│       └── parser.py       # .env.example parser
├── tests/
│   ├── test_resolver.py
│   ├── test_vault.py
│   ├── test_parser.py
│   └── conftest.py
├── pyproject.toml
├── LICENSE
├── README.md
├── AGENTS.md
└── .github/
    └── workflows/
        ├── ci.yml
        └── publish.yml     # PyPI publish on tag

pyproject.toml

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "envpact"
version = "1.0.0"
description = "Centralized, serverless secrets manager for solo developers"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
    { name = "Chirag Singhal", email = "[email protected]" },
]
keywords = [
    "secrets",
    "env",
    "dotenv",
    "secrets-manager",
    "environment-variables",
    "envpact",
]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Topic :: Security",
    "Topic :: Software Development :: Build Tools",
]

[project.scripts]
envpact = "envpact.cli:main"

[project.urls]
Homepage = "https://github.com/chirag127/envpact"
Repository = "https://github.com/chirag127/envpact"
Issues = "https://github.com/chirag127/envpact/issues"

Component 5: envpact-action (GitHub Action)

Marketplace Details

  • Name: envpact-action
  • Author: chirag127
  • License: MIT

Usage in Workflows

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Resolve Secrets
        uses: chirag127/envpact-action@v1
        with:
          vault-repo: chirag127/envpact-secrets
          vault-token: ${{ secrets.ENVPACT_VAULT_TOKEN }}
          project-name: ${{ github.event.repository.name }}

      # The action writes .env to the workspace
      - name: Build
        run: |
          source .env
          npm run build

Action Inputs

inputs:
  vault-repo:
    description: >
      The GitHub repository containing secrets.json
      (e.g., "chirag127/envpact-secrets")
    required: true
  vault-token:
    description: >
      A GitHub Personal Access Token (PAT) with read
      access to the vault repository
    required: true
  project-name:
    description: >
      The project name to resolve secrets for.
      Defaults to the current repository name.
    required: false
    default: ""
  output-file:
    description: >
      Path to write the resolved .env file
    required: false
    default: ".env"
  env-example:
    description: >
      Path to the .env.example file
    required: false
    default: ".env.example"
  export-to-env:
    description: >
      If true, also export resolved secrets as
      environment variables for subsequent steps
    required: false
    default: "false"
  sync-github-secrets:
    description: >
      If true, sync resolved secrets to GitHub
      repository secrets (requires admin PAT)
    required: false
    default: "false"

Action Outputs

outputs:
  resolved-count:
    description: >
      The number of secrets successfully resolved
  env-file-path:
    description: >
      The path to the generated .env file

Repository Structure

envpact-action/
├── action.yml           # Action definition
├── src/
│   ├── index.js         # Main action logic
│   ├── vault.js         # Vault fetch and parse
│   └── resolver.js      # Secret resolution
├── dist/
│   └── index.js         # Bundled output (ncc compiled)
├── tests/
│   └── action.test.js
├── package.json
├── LICENSE
├── README.md
├── AGENTS.md
└── .github/
    └── workflows/
        └── ci.yml

action.yml

name: "envpact"
description: >
  Resolve environment secrets from your private
  envpact vault and generate .env files for CI/CD
author: "chirag127"
branding:
  icon: "lock"
  color: "green"

inputs:
  vault-repo:
    description: "Private vault repository"
    required: true
  vault-token:
    description: "PAT with vault read access"
    required: true
  project-name:
    description: "Project name override"
    required: false
    default: ""
  output-file:
    description: ".env output path"
    required: false
    default: ".env"
  env-example:
    description: ".env.example path"
    required: false
    default: ".env.example"
  export-to-env:
    description: "Export as step env vars"
    required: false
    default: "false"
  sync-github-secrets:
    description: "Sync to repo secrets"
    required: false
    default: "false"

outputs:
  resolved-count:
    description: "Number of resolved secrets"
  env-file-path:
    description: "Generated .env path"

runs:
  using: "node20"
  main: "dist/index.js"

Component 6: envpact-vscode (VS Code Extension)

Extension Details

  • Marketplace ID: chirag127.envpact
  • Display Name: envpact
  • Language: TypeScript
  • License: MIT

Features

  1. Sidebar Panel: A dedicated sidebar showing:

    • All projects in the vault
    • Shared secrets (names only, masked values)
    • Current project's resolved secrets
  2. Commands (Command Palette):

    • envpact: Generate .env — Resolve and write .env
    • envpact: Initialize Vault — Set up vault connection
    • envpact: Add Secret — Add a secret to the current project via quick input
    • envpact: Add Shared Secret — Add a shared secret
    • envpact: Rotate Secret — Rotate a shared secret
    • envpact: Sync to GitHub — Sync secrets to GitHub Actions
    • envpact: List Projects — Show all projects
    • envpact: Refresh Vault — Pull latest from vault
  3. Status Bar: Shows the current project name and a lock icon indicating vault connection status.

  4. CodeLens: In .env.example files, show a CodeLens above each variable indicating whether it is resolved, missing, or shared.

  5. Hover Provider: Hovering over an environment variable name in any file shows its source (shared vs project-specific) and resolution status.

Repository Structure

envpact-vscode/
├── src/
│   ├── extension.ts       # Activation entry point
│   ├── commands/
│   │   ├── generate-env.ts
│   │   ├── init-vault.ts
│   │   ├── add-secret.ts
│   │   ├── rotate-secret.ts
│   │   └── sync-github.ts
│   ├── providers/
│   │   ├── sidebar.ts     # Tree view provider
│   │   ├── codelens.ts    # CodeLens provider
│   │   └── hover.ts       # Hover provider
│   └── lib/
│       ├── vault.ts       # Vault operations
│       └── resolver.ts    # Secret resolution
├── resources/
│   ├── icon.png           # Extension icon
│   └── sidebar-icon.svg
├── tests/
│   └── extension.test.ts
├── package.json           # VS Code extension manifest
├── tsconfig.json
├── LICENSE
├── README.md
├── AGENTS.md
└── .github/
    └── workflows/
        ├── ci.yml
        └── publish.yml    # vsce publish on tag

Component 7: envpact-dashboard (Web Dashboard)

Deployment

  • Hosting: Cloudflare Pages
  • URL: https://envpact.dev (or subdomain)
  • Framework: Vanilla HTML/CSS/JS (or lightweight framework like Astro)
  • Backend: None — purely client-side
  • Authentication: GitHub OAuth (to read/write the private vault repo via GitHub API)

Features

  1. GitHub OAuth Login: User authenticates with GitHub to grant read/write access to their private vault repo.

  2. Project Overview: Visual grid showing all projects, the number of secrets each has, and which shared secrets they reference.

  3. Secret Editor: A form-based interface to:

    • Add, edit, and delete project secrets
    • Add, edit, and delete shared secrets
    • Toggle between raw JSON view and form view
  4. Key Rotation Wizard: A guided flow to:

    • Select a shared secret
    • Enter the new value
    • See all projects that will be affected
    • Confirm and commit the change
  5. Audit Log: Shows the Git commit history of secrets.json to track who changed what and when.

  6. Export: Download resolved .env files for any project directly from the browser.

Security Model

  • The dashboard never stores secrets on any server.
  • All operations happen client-side using the GitHub API.
  • The GitHub OAuth token is stored in sessionStorage (cleared on tab close) — never localStorage.
  • The vault repo content is fetched via the GitHub Contents API and modified via the GitHub Commits API.

Repository Structure

envpact-dashboard/
├── src/
│   ├── index.html
│   ├── styles/
│   │   └── main.css
│   ├── scripts/
│   │   ├── app.js         # Main application logic
│   │   ├── auth.js        # GitHub OAuth flow
│   │   ├── vault.js       # GitHub API for vault ops
│   │   ├── resolver.js    # Client-side resolution
│   │   └── ui.js          # DOM manipulation
│   └── assets/
│       └── logo.svg
├── public/
│   ├── favicon.ico
│   └── ads.txt
├── wrangler.toml          # Cloudflare Pages config
├── package.json
├── LICENSE
├── README.md
├── AGENTS.md
└── .github/
    └── workflows/
        └── deploy.yml     # Cloudflare Pages deploy

Component 8: Umbrella Monorepo (envpact)

Repository Structure

envpact/                     # PUBLIC umbrella repo
├── envpact-cli/             # Git submodule
├── envpact-mcp/             # Git submodule
├── envpact-python/          # Git submodule (PyPI: envpact)
├── envpact-action/          # Git submodule
├── envpact-vscode/          # Git submodule
├── envpact-dashboard/       # Git submodule
├── .gitmodules
├── LICENSE
├── README.md                # Main project README
├── AGENTS.md                # Agent instructions
├── CONTRIBUTING.md
└── docs/
    ├── architecture.md
    ├── schema.md
    └── security.md

.gitmodules

[submodule "envpact-cli"]
  path = envpact-cli
  url = https://github.com/chirag127/envpact-cli.git

[submodule "envpact-mcp"]
  path = envpact-mcp
  url = https://github.com/chirag127/envpact-mcp.git

[submodule "envpact-python"]
  path = envpact-python
  url = https://github.com/chirag127/envpact.git

[submodule "envpact-action"]
  path = envpact-action
  url = https://github.com/chirag127/envpact-action.git

[submodule "envpact-vscode"]
  path = envpact-vscode
  url = https://github.com/chirag127/envpact-vscode.git

[submodule "envpact-dashboard"]
  path = envpact-dashboard
  url = https://github.com/chirag127/envpact-dashboard.git

Quick Start Guide

Step 1: Create Your Private Vault

# Option A: Let envpact create it automatically
npx envpact-cli --init auto

# Option B: Create manually
gh repo create envpact-secrets --private --clone
cd envpact-secrets
echo '{"shared":{},"projects":{},"version":1}' \
  > secrets.json
git add . && git commit -m "init" && git push
cd ..
npx envpact-cli --init \
  [email protected]:YOUR_USER/envpact-secrets.git

Step 2: Use in Any Project

cd my-project

# Ensure .env.example exists with required keys
cat .env.example
# OPENAI_API_KEY=
# DATABASE_URL=
# PORT=3000

# Generate .env
npx envpact-cli
# → Resolves from vault, prompts for missing,
#   writes .env

# Sync to GitHub Actions
npx envpact-cli --github

Step 3: Configure AI Agents

{
  "mcpServers": {
    "envpact": {
      "command": "npx",
      "args": ["-y", "envpact-mcp"]
    }
  }
}

Step 4: Use in Python Projects

pip install envpact
python -m envpact

Step 5: Use in CI/CD

- uses: chirag127/envpact-action@v1
  with:
    vault-repo: your-user/envpact-secrets
    vault-token: ${{ secrets.ENVPACT_VAULT_TOKEN }}

Key Rotation Workflow

When you need to rotate a secret (e.g., your OpenAI API Key is compromised):

Using the CLI

# Rotate interactively
npx envpact-cli --rotate OPENAI_API_KEY
# → Prompts for new value
# → Updates secrets.json
# → Commits and pushes to vault

# Then, in each affected project:
cd my-project && npx envpact-cli
cd ../other-project && npx envpact-cli --github

Using the MCP Server

Ask your AI agent:

"Rotate my OPENAI_API_KEY to sk-new-key-here and sync it to GitHub for all projects."

The agent calls rotate_secret followed by sync_github for each affected project.

Using the Python Module

from envpact import EnvPact

pact = EnvPact()
pact.rotate("OPENAI_API_KEY", "sk-new-key-here")
pact.sync_github(project_name="my-app")
pact.sync_github(project_name="my-saas")

Using the Dashboard

  1. Log in at envpact.dev
  2. Click on "Shared Secrets"
  3. Click the rotate icon next to OPENAI_API_KEY
  4. Enter the new value
  5. Review affected projects
  6. Click "Rotate & Commit"

Security Best Practices

1. Keep the Vault Private

Never make the envpact-secrets repository public. This is the single most important security rule. The entire security model depends on this repository being private.

2. Use SSH Keys for Vault Access

Configure SSH-based Git access to your vault repository. This avoids storing HTTPS credentials and leverages your existing SSH key infrastructure.

# Clone via SSH (recommended)
npx envpact-cli --init \
  [email protected]:YOUR_USER/envpact-secrets.git

# NOT via HTTPS (avoid)
# npx envpact-cli --init \
#   https://github.com/YOUR_USER/envpact-secrets.git

3. Global .gitignore

Add .env to your global Git ignore to prevent accidental leaks in any repository:

git config --global core.excludesfile \
  ~/.gitignore_global
echo ".env" >> ~/.gitignore_global
echo ".env.local" >> ~/.gitignore_global
echo ".env.*.local" >> ~/.gitignore_global

4. Hardware Key MFA

Secure your GitHub account with hardware key MFA (YubiKey or similar). Your vault is only as secure as your GitHub account.

5. Minimal PAT Scopes

When creating a PAT for the GitHub Action (envpact-action), use the minimum required scopes:

  • repo (for private repo read access)
  • Or use a fine-grained PAT scoped to only the envpact-secrets repository with "Contents: Read" access.

6. Audit the Commit History

The vault's Git history serves as a complete audit log. Every secret change is recorded with a timestamp and commit message. Use git log on the vault repo to review all changes.


Competitive Comparison

FeatureenvpactdotenvxDopplerInfisical1Password
Cost (100 projects)$0$0$252/yr$216/yr$36/yr
CentralizedYesNoYesYesYes
DRY (shared refs)YesNoYesYesNo
Local .env genYesYesYesYesYes
GitHub syncYesNoYesYesNo
MCP serverYesNoNoNoNo
Python moduleYesNoNoYesNo
VS Code extYesNoNoNoYes
GitHub ActionYesNoYesYesNo
Zero dependenciesYesYesNoNoNo
Self-hostedGitGitCloudCloud/VPSCloud
Offline capableYesYesNoNoNo
AI-agent readyYesPartialNoNoNo

Naming Convention Reference

All public-facing names follow a consistent pattern:

ContextName
Umbrella repochirag127/envpact
CLI repochirag127/envpact-cli
MCP repochirag127/envpact-mcp
Python repochirag127/envpact (PyPI name)
Action repochirag127/envpact-action
VS Code repochirag127/envpact-vscode
Dashboard repochirag127/envpact-dashboard
Private vault{username}/envpact-secrets
npm CLIenvpact-cli
npm MCPenvpact-mcp
PyPIenvpact
VS Code Marketplaceenvpact
GitHub Actionchirag127/envpact-action@v1
CLI binaryenvpact
Config directory~/.envpact/
Vault subdirectory~/.envpact/secrets/

Implementation Priority

For any agent building this ecosystem, the recommended implementation order is:

Phase 1: Core (Ship First)

  1. envpact-cli — The CLI is the foundation. Everything else depends on the vault resolution logic.
  2. envpact-secrets — Document the vault schema and auto-creation flow.

Phase 2: AI Integration

  1. envpact-mcp — The MCP server reuses the CLI's resolution engine, just wrapping it in MCP tool calls.

Phase 3: Ecosystem Expansion

  1. envpact (Python) — Port the resolution logic to Python. Zero dependencies. Use subprocess for Git.
  2. envpact-action — GitHub Action wrapping the CLI for CI/CD. Use @actions/core and @actions/exec.

Phase 4: Developer Experience

  1. envpact-vscode — VS Code extension for GUI-based secret management.
  2. envpact-dashboard — Web dashboard for visual management via GitHub OAuth + API.

Phase 5: Polish

  1. envpact (umbrella) — Create the parent monorepo, add all submodules, write the master README.

AGENTS.md Template

Every repository in the ecosystem should include an AGENTS.md file with the following content (adapted per component):

# AGENTS.md — envpact-{component}

## Project Context

This is the {component} of the envpact ecosystem —
a centralized, serverless secrets manager for solo
developers.

## Architecture

- Central vault: private Git repo with secrets.json
- Resolution: shared.KEY_NAME references resolve to
  the shared section of secrets.json
- Local config: ~/.envpact/secrets/ (cloned vault)

## Key Files

- {entry-point}: Main entry point
- {lib/}: Core business logic
- {tests/}: Test suite

## Conventions

- Zero external dependencies (Node.js stdlib only
  for CLI, Python stdlib only for Python module)
- CommonJS for CLI, ESM for MCP server
- All paths use path.join() (cross-platform)
- Error messages are user-friendly and actionable
- Git operations use execSync/subprocess (no libgit2)

## Testing

- Use Node.js native test runner (node --test)
  for Node.js components
- Use pytest for Python components
- Mock filesystem and Git operations in tests
- Test resolution logic exhaustively:
  - Direct values
  - shared.KEY references
  - Missing keys (prompt flow)
  - Invalid JSON
  - Missing .env.example
  - Git clone/pull failures

## Security Rules

- NEVER log or print secret values
- NEVER include secret values in error messages
- Always mask values in --list-shared output
- Always validate secrets.json schema before use
- Handle Git authentication failures gracefully

README.md Template (Umbrella Repo)

The main envpact umbrella README should be the primary landing page for the project. It should include:

  • Title: "envpact — Centralized, Serverless Secrets Manager for Solo Developers"
  • Badges: NPM version for envpact-cli, PyPI version for envpact, MIT license badge
  • Tagline: "A $0, serverless, Git-backed secrets management ecosystem for developers managing 100+ public GitHub repositories."
  • Why envpact? section with bullet points:
    • One secret, one place
    • DRY rotation
    • AI-ready (MCP server)
    • Multi-runtime
    • $0 forever
  • Quick Start section with npx envpact-cli --init auto
  • Ecosystem table:
ComponentInstall Command
CLInpx envpact-cli
MCP ServerAdd to AI agent MCP config
Pythonpip install envpact
GitHub Actionchirag127/envpact-action@v1
VS CodeSearch "envpact" in Extensions
DashboardVisit envpact.dev
  • Architecture diagram (the ASCII art from the Architecture Overview section above)
  • Security Best Practices summary
  • Contributing guidelines
  • License (MIT)

Conclusion

envpact is not just another dotenv tool. It is a complete secrets management philosophy built on the principle that solo developers deserve the same centralized, DRY, rotation-friendly secrets management that enterprise teams get from Doppler or HashiCorp Vault — but at $0 cost, with zero infrastructure, and with first-class AI agent support.

The entire ecosystem is open source (MIT licensed), uses zero external runtime dependencies, and runs on infrastructure every developer already has: Git and GitHub.

Every component specification in this document is detailed enough for an AI coding agent to implement. The naming is consistent (envpact-*), the APIs are defined, the schemas are specified, the repository structures are outlined, and the implementation priority is clear.

Build it. Ship it. Never manually manage .env files again.


License

MIT — open source and free for all developers.

Comments

Comments are powered by giscus. Set PUBLIC_GISCUS_REPO_ID and PUBLIC_GISCUS_CATEGORY_ID in your environment to enable them.