Metadata-Version: 2.4
Name: github-secrets-manager
Version: 0.0.1
Summary: Manage local plaintext copies of GitHub Actions repository secrets and sync them to GitHub.
Author: Kaizten Analytics
License-Expression: LicenseRef-Proprietary
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# GitHub Secrets Manager

`github-secrets-manager.py` manages local plaintext copies of GitHub Actions
repository secrets and shared organization secret values, then syncs them to
GitHub by using the authenticated GitHub CLI (`gh`).

GitHub does not allow reading secret values after they are created. This script
solves that by keeping local copies of the values, plus local creation and update
timestamps, so repository secrets can be recreated or updated from your machine.
For organizations, one local shared secret can be applied to every repository
that already has a remote secret with the same name.

## Requirements

- Python 3
- GitHub CLI (`gh`)
- An authenticated GitHub CLI session:

```bash
gh auth login
```

The script manages GitHub Actions repository secrets. It does not manage
environment, organization-level, Dependabot, Codespaces, or Agents secrets.

## Local Storage

By default, local secrets are stored under:

```text
~/.github-local-secrets
```

You can change that location with `--secrets-dir`:

```bash
./github-secrets-manager.py --secrets-dir ~/secure/github-secrets local list octocat/hello-world
```

Repository-specific local secrets are stored using this layout:

```text
SECRETS_DIR/
  OWNER/
    REPO/
      metadata.json
      secrets/
        SECRET_NAME.secret
```

Organization shared local secrets are stored using this layout:

```text
SECRETS_DIR/
  organizations/
    ORG/
      metadata.json
      secrets/
        SECRET_NAME.secret
```

Each local secret value is stored as a plaintext UTF-8 file. Secret files are
written with `0600` permissions. Protect the configured secrets directory as
sensitive material and do not commit it to Git.

`metadata.json` stores local metadata for each secret:

- `created_at`
- `updated_at`
- relative local file path

## Usage

```bash
./github-secrets-manager.py [--secrets-dir DIR] {local,repo,org} ...
```

Use `--help` at any level to see the available commands:

```bash
./github-secrets-manager.py --help
./github-secrets-manager.py local --help
./github-secrets-manager.py repo --help
./github-secrets-manager.py org --help
./github-secrets-manager.py org local --help
```

## Local Commands

Create a local secret from standard input:

```bash
printf 'my-secret-value\n' | ./github-secrets-manager.py local create octocat/hello-world API_KEY --stdin
```

Create a local multiline secret:

```bash
./github-secrets-manager.py local create octocat/hello-world PRIVATE_KEY --stdin <<'EOF'
-----BEGIN PRIVATE KEY-----
line 1
line 2
-----END PRIVATE KEY-----
EOF
```

Create a local secret from a file:

```bash
./github-secrets-manager.py local create octocat/hello-world NPM_TOKEN --value-file ./npm-token.txt
```

Update or create a local secret:

```bash
printf 'new-value\n' | ./github-secrets-manager.py local update octocat/hello-world API_KEY --stdin
```

List local secrets and their metadata:

```bash
./github-secrets-manager.py local list octocat/hello-world
```

Show a local secret value:

```bash
./github-secrets-manager.py local show octocat/hello-world API_KEY
```

Delete a local secret:

```bash
./github-secrets-manager.py local delete octocat/hello-world API_KEY
```

## Repository Commands

List GitHub Actions secrets configured in a repository:

```bash
./github-secrets-manager.py repo list octocat/hello-world
```

GitHub only returns secret names and update metadata. It never returns secret
values.

Create or update one GitHub secret from its local value:

```bash
./github-secrets-manager.py repo create octocat/hello-world API_KEY
```

Preview that operation without writing to GitHub:

```bash
./github-secrets-manager.py repo create octocat/hello-world API_KEY --dry-run
```

Update selected GitHub secrets from local values:

```bash
./github-secrets-manager.py repo update octocat/hello-world --secrets API_KEY,NPM_TOKEN
```

Update all remote secrets that have matching local values:

```bash
./github-secrets-manager.py repo update octocat/hello-world --all
```

Preview repository updates without writing to GitHub:

```bash
./github-secrets-manager.py repo update octocat/hello-world --all --dry-run
```

Compare local and remote secrets:

```bash
./github-secrets-manager.py repo sync octocat/hello-world
```

The sync report uses these statuses:

- `remote-and-local`: the secret exists in GitHub and locally.
- `missing-local`: the secret exists in GitHub but no local value is available.
- `local-only`: the secret exists locally but not in GitHub.

Remote secrets that are missing locally are reported only. They are not deleted
or overwritten.

## Organization Commands

Organization local secrets are stored once per organization and can be synced to
repositories in that organization. The sync still writes repository-level GitHub
Actions secrets; it does not create GitHub organization-level secrets.

Create an organization local secret from standard input:

```bash
printf 'shared-token-value\n' | ./github-secrets-manager.py org local create my-organization API_TOKEN --stdin
```

Create an organization local multiline secret:

```bash
./github-secrets-manager.py org local create my-organization PRIVATE_KEY --stdin <<'EOF'
-----BEGIN PRIVATE KEY-----
line 1
line 2
-----END PRIVATE KEY-----
EOF
```

Create an organization local secret from a file:

```bash
./github-secrets-manager.py org local create my-organization NPM_TOKEN --value-file ./npm-token.txt
```

Update or create an organization local secret:

```bash
printf 'new-shared-value\n' | ./github-secrets-manager.py org local update my-organization API_TOKEN --stdin
```

List organization local secrets:

```bash
./github-secrets-manager.py org local list my-organization
```

Show an organization local secret value:

```bash
./github-secrets-manager.py org local show my-organization API_TOKEN
```

Delete an organization local secret:

```bash
./github-secrets-manager.py org local delete my-organization API_TOKEN
```

Update matching repository secrets across an organization:

```bash
./github-secrets-manager.py org sync my-organization
```

The command lists repositories in the organization and updates only remote
repository secrets that have matching organization local values. If a repository
does not already have a remote secret with the same name, the script skips it.

Sync only selected organization local secrets:

```bash
./github-secrets-manager.py org sync my-organization --secrets API_TOKEN,NPM_TOKEN
```

Preview organization-wide updates without writing to GitHub:

```bash
./github-secrets-manager.py org sync my-organization --dry-run
```

Include archived repositories:

```bash
./github-secrets-manager.py org sync my-organization --include-archived
```

Use a custom local secrets directory for an organization sync:

```bash
./github-secrets-manager.py \
  --secrets-dir ~/secure/github-secrets \
  org sync my-organization \
  --dry-run
```

## Example Workflow

Create shared local secrets for an organization:

```bash
printf 'token-value\n' | ./github-secrets-manager.py org local create my-org API_TOKEN --stdin
./github-secrets-manager.py org local create my-org PRIVATE_KEY --value-file ./private-key.pem
```

Review what is stored locally:

```bash
./github-secrets-manager.py org local list my-org
```

Preview the organization-wide GitHub update:

```bash
./github-secrets-manager.py org sync my-org --dry-run
```

Apply the shared values to matching repository secrets:

```bash
./github-secrets-manager.py org sync my-org
```

Apply only one shared value:

```bash
./github-secrets-manager.py org sync my-org --secrets API_TOKEN
```

Use repository-local commands for repository-specific exceptions that should not
come from the shared organization store.

## Safety Notes

- Local secret values are plaintext. Keep the secrets directory private.
- Do not store the secrets directory inside a Git repository.
- Secret values are sent to `gh secret set` through standard input, not through
  command-line arguments.
- Use `--dry-run` before repository or organization updates when you want to
  inspect what would change.
- The script validates repository names as `OWNER/REPO` and secret names as
  uppercase/underscore-compatible identifiers, though lowercase letters are also
  accepted.
