GitHub Workflow Manager CLI
Install managed GitHub workflow files from a central marketplace repository.
Workflows can be sourced from the official ghwfxlab/ghwm-registry or your own registry repository (see Create your own registry). Workflow authors can annotate packages with presentation metadata using commented YAML frontmatter.
Why ghwm?
Teams copy the same GitHub Actions workflows from repository to repository. Over time the copies drift, nobody knows which version or edit is running where, and security fixes never reach every repository.
ghwm treats workflows as versioned packages. You list them in ghwm.yml, ghwm install writes them to .github/workflows/, and ghwm.lock records exactly what was installed. Updates keep your local on: triggers by default, and files that ghwm does not manage are skipped unless you pass --force.
- Compared with reusable workflows: a reusable workflow is called remotely, so the logic lives outside your repository and you can't tune the file.
ghwminstalls a real file you can read, review and adjust. - Compared with starter workflows: a starter workflow is a one-time copy with no update path.
ghwmtracks versions and hashes, soghwm updatebrings in new releases.
Built-in security
Run ghwm audit to scan every managed workflow with zizmor and get a Security Score from 0 to 100. It exits with 1 on High or Medium findings, so it works as a CI gate. See Auditing Workflows.
Install
The recommended way to install ghwm is using uv (a fast Python package manager):
uv tool install git+https://github.com/ghwfxlab/ghwm.git
Or pin to a specific version tag:
uv tool install git+https://github.com/ghwfxlab/ghwm.git@vX.Y.Z
Alternatives
If you prefer to install using pipx:
pipx install git+https://github.com/ghwfxlab/ghwm.git
Or via standard pip (into an active virtual environment):
pip install git+https://github.com/ghwfxlab/ghwm.git
Usage
1. Create ghwm.yml in your repository root
source: owner/ghwm-registry
workflows:
- name: linter
version: "1.0.0"
- name: auto-assign-pr
version: "1.0.0"
source: other-owner/other-registry # (Optional) Override the global registry source
update-triggers: true
update-envs: true
update-config-files: true
version is required for registry installs. By default, workflows are resolved from the global source. You can override this on a per-workflow basis using the source property. The CLI resolves each workflow to a GitHub Packages npm
package named @<source owner>/<name>.
If you want to override the generated workflow filename, add target: my-review.yml to the
workflow entry. For the complete schema, field types, and defaults, see the Manifest and Lockfile Reference.
2. Install workflows
ghwm install # installs workflows listed in ghwm.yml
ghwm install --force # overwrites even if files were modified locally
ghwm install --no-prune # skip removal of stale workflows
ghwm install --update-triggers # replace workflow triggers with the packaged version
ghwm install --update-envs # replace workflow env variables with the packaged version
ghwm upgrade # automatically resolve, pin, and install latest versions
ghwm update # re-downloads all workflows (respects versions)
ghwm update --prune # also removes managed workflows no longer in ghwm.yml
ghwm update --update-triggers # replace workflow triggers with the packaged version
ghwm update --update-envs # replace workflow env variables with the packaged version
ghwm list # shows workflows declared in ghwm.yml
ghwm audit # audits managed workflows for security vulnerabilities
Workflow files are written to .github/workflows/ with a managed header. Additional packaged files
such as config files are written as-is. On first install, config files are only created if the target
does not already exist. On update, config files are only overwritten when
update-config-files: true is set for that workflow.
| File type | First install | Update | Prune |
|---|---|---|---|
Workflow file (.github/workflows/*) |
Install with managed header | Update in place; preserve existing on: by default |
Remove |
| Packaged config file | Create only when missing | Overwrite only with update-config-files: true |
Keep |
Managed files
Every installed workflow file starts with a header that marks it as managed:
# Managed by ghwm (linter@0.1.4)
# Source: @owner/linter:linter.yml
# Hash: sha256:...
# Re-run `ghwm install` to refresh this file.
The CLI uses this header to:
- Distinguish managed files from hand-crafted ones (unmanaged files are never
overwritten without
--force). - Preserve existing
on:rules during updates unlessupdate-triggers: trueor--update-triggersis used. - Preserve existing
env:variables during updates unlessupdate-envs: trueor--update-envsis used. - Prune stale workflow files that were removed from the manifest.
Config files do not get a managed header and are never removed during prune.
Use ghwm update --prune when you want one command to refresh workflows that are still in
ghwm.yml and remove managed workflows that were deleted from the manifest. It updates
ghwm.lock too.
Lockfile
ghwm.lock is a JSON file that records every installed package and the files it manages:
{
"lockfileVersion": 1,
"packages": [
{
"name": "auto-assign-pr",
"version": "2.0.0",
"source": "@owner/auto-assign-pr",
"files": [
{
"target": ".github/workflows/auto-assign-pr.yaml",
"source_hash": "sha256:..."
},
{
"target": ".github/auto_assign.yaml",
"source_hash": "sha256:...",
"overwrite": false
}
]
}
]
}
Commit ghwm.lock alongside ghwm.yml so CI and teammates install the exact same
workflow package set. Old tarball-era lockfiles are rejected and must be regenerated.
Auditing Workflows (Security Scoring)
Since workflows are imported from shared registry repositories, keeping them secure is critical. The ghwm audit command runs static security analysis on all your installed/managed workflows using zizmor (a fast security linter for GitHub Actions).
ghwm audit
If the linter is not installed locally, ghwm will attempt to execute it dynamically using uvx zizmor.
ghwm audit calculates a Security Score out of 100 on a logarithmic scale (exponential decay) to ensure the score never goes negative:
$$\text{Score} = \text{round}\left(100 \times e^{-\text{Deductions} / 100}\right)$$
where total Deductions are calculated from:
- High severity: 20 points
- Medium severity: 10 points
- Low severity: 5 points
- Informational: 1 point
If any High or Medium severity vulnerabilities are detected, ghwm audit exits with code 1, making it ideal for integration into CI pipelines.
Keep Workflows Updated (Renovate Integration)
Since ghwm resolves workflows to npm packages (e.g., @owner/<name>) published to GitHub Packages, you can use Renovate to automatically detect updates and open pull requests to update the versions in your ghwm.yml.
Add the following regexManagers configuration to your renovate.json or renovate.json5 file:
{
"regexManagers": [
{
"fileMatch": ["^ghwm\\.yml$"],
"matchStrings": [
"name:\\s+(?<depName>\\S+)\\s+version:\\s+[\"']?(?<currentValue>[^\"'\\s]+)[\"']?"
],
"datasourceTemplate": "npm",
"depNameTemplate": "@owner/{{depName}}",
"registryUrlTemplates": ["https://npm.pkg.github.com"]
}
]
}
Note: Replace @owner with the GitHub organization or username where your registry packages are published.
Authentication
The CLI needs read access to GitHub Packages and the marketplace repository. The CLI looks for authentication in the following order:
- Active
ghCLI credentials (using the output ofgh auth token) GH_TOKENenvironment variableGITHUB_TOKENenvironment variable
For example, you can set an environment variable:
export GH_TOKEN=ghp_...
# or
export GITHUB_TOKEN=ghp_...
If you rely on the gh CLI, ensure that its active token has read:packages access. You can add this scope if needed by running:
gh auth refresh -s read:packages
Local Development
Point at a local checkout of the registry for testing:
ghwm install --local ../ghwm-registry
With --local, the CLI reads workflows/<name>/workflow.yml directly from the checkout instead of
downloading the npm package tarball.
Telemetry & Privacy
ghwm collects anonymous, privacy-gated usage telemetry to understand which workflow packages are being adopted. Here is exactly what is tracked and what is not:
What is tracked:
| Field | Example |
|---|---|
| Registry source | owner/ghwm-registry |
| Workflow name | linter |
| Workflow version | 1.2.3 |
| Event type | install (first time added) or updated (workflow changed on re-install) |
| Workflow metadata (optional) | Title, description, tags, icon, owner, version, source, created_at (see Workflow Frontmatter Specification) |
What is never tracked: IP addresses, hostnames, usernames, file paths, repository names, environment variables, or any other system or user information.
Privacy gate
Telemetry is only emitted when the workflow registry is a public GitHub repository. Before sending any data, ghwm calls the GitHub REST API (GET /repos/{owner}/{repo}) without authentication to check the visibility of each workflow's resolved source registry. If a registry is private, internal, or if the check fails for any reason (network error, rate limit, missing permissions), telemetry is skipped silently for that workflow and the install continues normally.
This means workflows sourced from private or internal enterprise registries are never tracked, protecting proprietary workflow IP.
Opting out
You can disable telemetry for a single run with the --no-telemetry flag:
ghwm install --no-telemetry
ghwm update --no-telemetry
ghwm upgrade --no-telemetry
To opt out permanently — for example in CI/CD pipelines or a global shell config — set either the DO_NOT_TRACK=1 environment variable (consoledonottrack.com) or GHWM_NO_TELEMETRY=1:
export DO_NOT_TRACK=1
# or
export GHWM_NO_TELEMETRY=1
Create your own registry
A registry is a GitHub repository whose workflows/ folder holds one npm package per workflow, published to GitHub Packages. The official ghwfxlab/ghwm-registry is a good reference to copy from.
-
Create a repository, for example
my-org/ghwm-registry. The owner becomes the npm scope (@my-org/<name>). -
Add a folder per workflow, such as
workflows/linter/, containing the workflow file, any config files, apackage.jsonand aworkflow.yml:# workflows/linter/workflow.yml name: linter files: - source: linter.yaml target: .github/workflows/linter.yaml
Optionally add commented frontmatter with a title, description and tags.
-
Set
"name": "@my-org/linter"and"publishConfig": {"registry": "https://npm.pkg.github.com"}inpackage.json, and list the same files under"files". -
Test locally from a consumer repository, without publishing:
ghwm install --local ../ghwm-registry
-
Publish each package to GitHub Packages (the official registry does this from a publish workflow on every push to
main), then set the package visibility to Public if consumers outside your organization need it. -
Point consumers at it in
ghwm.yml:source: my-org/ghwm-registry workflows: - linter@1.0.0
Development
make install # uv sync (dev deps)
make test # pytest
make lint # ruff check
make format # ruff format
make type-check # mypy
make lang # textlint for prose/documentation
make precommit # run pre-commit hooks on all files
make super-linter # run super-linter via Docker
make clean # remove build artifacts
Architecture
See ARCHITECTURE.md for design details.
License
MIT
Metadata
Release files for ghwm 1.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ghwm-1.10.0.tar.gz | 275.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ghwm-1.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 306.7 kB
Release files / ghwm-1.10.0.tar.gz
| Download URL | ghwm-1.10.0.tar.gz |
|---|---|
| Size | 275.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b19afa3500db6e117edbb8fde31b73dbd63b7df6d98c0a42a6199e43f9c61d9a
|
|
BLAKE2b-256 checksum How to use checksums |
19b9e57b2b80e9d6d7f7e2b556f9d0c91ca2518b67bd6c78e97919c010591b6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / ghwm-1.10.0-py3-none-any.whl
| Download URL | ghwm-1.10.0-py3-none-any.whl |
|---|---|
| Size | 31.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e8dae2648c494825499c7ec3e022f1096d0c7772c33194a0c74eeec48d1b1f37
|
|
BLAKE2b-256 checksum How to use checksums |
bf1ffe27ec02f03a57ed3c53ee03a17f0af26f4a983c8de4468527944dab3978
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log