Manage GitHub environment variables and secrets for a repository from a JSON config file — validates, confirms, and applies directly via gh CLI. Can also scan a project codebase and generate a template config with placeholder values.
Manage GitHub environment variables and secrets for a repository from a JSON config file.
The skill validates the config, verifies the target repo, shows a dry-run summary, asks for explicit confirmation, then applies changes directly via gh CLI. No intermediate script is generated — secrets never touch disk.
If $ARGUMENTS is empty, run init for the current working directory — do not print usage, do not wait for input. Proceed directly to the Init mode section using . as the project path.
$ARGUMENTS: either:
init for the current working directory (see Init mode below)init [project-path] — scan a project codebase and create .github/env-config.json (see Init mode below)apply — auto-detect the current repo and apply (see below).github/env-config.jsonIf the first argument is init, follow the Init mode section.
If the first argument is apply, follow the Apply mode section.
If arguments are empty, follow the Init mode section using . as the project path.
****)@file: references, verify the referenced file exists before proceeding"@remove" in the configgh repo view before showing the summary — abort if the repo doesn't exist or the user lacks access{
"environments": {
"qa": {
"variables": {
"AZURE_WEBAPP_NAME": "app-qa",
"APP_URL": "https://qa.example.com",
"OLD_SETTING": "@remove"
},
"secrets": {
"APPSETTINGS_JSON": "@file:./secrets/qa/appsettings.json",
"AZURE_DEPLOYMENT_TOKEN": "token-qa",
"LEGACY_KEY": "@remove"
}
},
"prod": {
"variables": {
"AZURE_WEBAPP_NAME": "app-prod",
"APP_URL": "https://example.com"
},
"secrets": {
"APPSETTINGS_JSON": "@file:./secrets/prod/appsettings.json",
"AZURE_DEPLOYMENT_TOKEN": "token-prod"
}
}
}
}
environments: keyed by environment name (qa, prod, etc.), each with variables and secrets@file:)When a value starts with @file:, it means "read the secret value from this file path". This is used for secrets whose value is an entire file (e.g. appsettings.json, XML publish profiles).
@file:./path (never print file contents)gh secret set — never store it in a variable or print itThis avoids having to escape JSON-inside-JSON or large XML blobs.
@remove)When a value is exactly "@remove", the variable or secret will be deleted from the environment.
variables and secretsREMOVE: lines so they stand outWhen the first argument is init (or when no arguments are provided), scan a project's codebase to discover which GitHub environment variables and secrets it expects, then write .github/env-config.json with placeholder values.
init, use it. Otherwise use the current working directory..github/ or src/ or a .sln/package.json)..github/env-config.json already exists in the resolved project path, print a message and stop — do not overwrite:
.github/env-config.json already exists. Edit it directly or delete it to re-init.
Scan these sources in order, collecting variable and secret names per environment:
GitHub workflow files (.github/workflows/*.yml):
vars.<NAME> references → add to variablessecrets.<NAME> references → add to secretsenvironment: fields in jobs (e.g. qa, prod)secrets.GITHUB_TOKEN (built-in, not user-managed)Existing .github/appsettings-value/ directory (if present):
appsettings.json, add APPSETTINGS_JSON to that environment's secrets with value @file:./secrets/<env>/appsettings.jsonTerraform files (terraform/*.tf, infra/*.tf):
var. references that map to GitHub variables (e.g. variables passed via -var in workflow files)For each discovered entry, assign a sensible placeholder:
"<VARIABLE_NAME>" as placeholder (e.g. "<AZURE_WEBAPP_NAME>")@file: pattern (like APPSETTINGS_JSON): use "@file:./secrets/<env>/appsettings.json""<SECRET_NAME>" as placeholder (e.g. "<AZURE_DEPLOYMENT_TOKEN>")If no environments were detected, default to qa and prod.
If a variable or secret appears in workflows without a specific environment context (e.g. referenced in a job with no environment: field), include it in all detected environments.
Write the generated JSON to <project-path>/.github/env-config.json.
Before writing, show the user a preview of the generated JSON and the output path. Ask for confirmation before writing.
(The file-already-exists check happens in Init 1 — by this step the file is guaranteed not to exist.)
After writing, check whether .github/env-config.json is covered by <project-path>/.gitignore:
.gitignore (if it exists)..github/env-config.json (or a pattern that matches it, e.g. env-config.json or .github/) is not already present, append the following line to .gitignore:
.github/env-config.json
.github/env-config.json already in .gitignore — no change needed..github/env-config.json to .gitignore.If .gitignore does not exist, create it with .github/env-config.json as the sole entry.
After writing, print:
Config written to <path>/.github/env-config.json
Next steps:
1. Fill in the placeholder values (search for < and >)
2. For @file: secrets, place the actual files at the referenced paths
3. Apply with: /repo-git-env apply
The following steps apply when the skill is invoked with apply.
Run:
gh repo view --json nameWithOwner,url
from the current working directory (no repo argument — gh resolves it from the git remote automatically).
nameWithOwner (e.g. my-org/my-repo) and url for display.Look for .github/env-config.json relative to the current working directory.
.github/env-config.json not found. Run /repo-git-env init first.
and stop.Read the JSON config file. Validate:
environments with at least one environmentsecrets or variables (print which keys are empty and stop)@file: references point to files that exist, resolved relative to .github/ (print which are missing and stop)@remove values are valid in both variables and secrets — they mark items for deletionBefore doing anything, present the full picture to the user — what was auto-detected and what will happen:
──────────────────────────────────────
REPO: my-org/my-repo
https://github.com/my-org/my-repo
CONFIG: .github/env-config.json
──────────────────────────────────────
[qa]
SET variables: AZURE_WEBAPP_NAME = app-qa, APP_URL = https://qa.example.com
SET secrets: APPSETTINGS_JSON = @file:./secrets/qa/appsettings.json
AZURE_DEPLOYMENT_TOKEN = ****
REMOVE: OLD_SETTING (variable), LEGACY_KEY (secret)
[prod]
SET variables: AZURE_WEBAPP_NAME = app-prod, APP_URL = https://example.com
SET secrets: APPSETTINGS_JSON = @file:./secrets/prod/appsettings.json
AZURE_DEPLOYMENT_TOKEN = ****
Total: 4 variables to set, 4 secrets to set, 1 variable to remove, 1 secret to remove
──────────────────────────────────────
Then ask:
Apply these changes to my-org/my-repo? Type YES to confirm, or anything else to cancel.
Do NOT proceed unless the user responds with exactly YES. Any other response = cancel.
Execute gh commands one by one, printing progress for each:
Create environments if they don't exist:
gh api --method PUT "repos/<owner/repo>/environments/<env-name>"
Set variables (non-@remove):
gh variable set <NAME> --body "<value>" --repo <owner/repo> --env <env>
Set secrets with plain values (non-@file:, non-@remove):
gh secret set <NAME> --body "<value>" --repo <owner/repo> --env <env>
Set secrets with @file: references — read and pipe directly:
cat "<resolved-path>" | gh secret set <NAME> --repo <owner/repo> --env <env>
Remove variables marked @remove:
gh variable delete <NAME> --repo <owner/repo> --env <env> --yes
Remove secrets marked @remove:
gh secret delete <NAME> --repo <owner/repo> --env <env> --yes
For each command, print a status line:
[qa] SET variable AZURE_WEBAPP_NAME ✓
[qa] SET secret APPSETTINGS_JSON ✓
[qa] REMOVE variable OLD_SETTING ✓
...
If any command fails, print the error for that item and continue with the remaining items (don't abort the whole batch). At the end, print a final summary:
Done. 8 succeeded, 0 failed.
If there were failures, list them so the user can retry manually.