Agent Academy

Agent Academy is a free, source-linked library of skills, prompts, MCP servers and bot templates, organised into courses by profession, for people who hand real work to AI agents: 1,467 live resources, each with its original link, license and safety tags.

Skill

extract-python-environment-variables

Scans a Python recipe to find every place an environment variable is accessed — including `os.environ.setdefault("V", "d")`, whose "d" would otherwise be a hidden default a user editing .env.example has no way to discover — then ensures all variables are declared in `.env.example`, that `load_dotenv()` is bootstrapped in the package `__init__.py`, and that `python-dotenv>=1.0.0` is listed in `pyproject.toml`. When…

No credentialsDoes not send

https://github.com/google/adk-recipes/blob/main/.agents/skills/extract-python-environment-variables/SKILL.md

Install
npx skills add google/adk-recipes --skill extract-python-environment-variables

Original content

Full originalApache-2.0Source

Republished in full under its license, with attribution. The source is authoritative.

name: extract-python-environment-variables
description: >
  Scans a Python recipe to find every place an environment variable is
  accessed — including `os.environ.setdefault("V", "d")`, whose "d" would
  otherwise be a hidden default a user editing .env.example has no way to
  discover — then ensures all variables are declared in `.env.example`,
  that `load_dotenv()` is bootstrapped in the package `__init__.py`, and
  that `python-dotenv>=1.0.0` is listed in `pyproject.toml`. When a new
  entry is added to `.env.example`, the extracted default from source is
  written as the value (with a `# extracted-by:extract-env-vars` marker
  and provenance comment); values that look like stubs
  (`"my-project-id"`, `"changeme"`, `"<...>"`) are downgraded to the TODO
  placeholder but the source string is preserved in the marker comment.
  Also detects hardcoded model-name string literals (e.g.
  `"gemini-3.5-flash"` in `agent.py`) and rewrites them to an
  `os.getenv(...)` call. The variable name is derived from the assignment
  target when it names a model (`DEFAULT_EMBEDDING_MODEL` →
  `EMBEDDING_MODEL`), else `MODEL_NAME` (single model) or
  `MODEL_NAME_GENERATED_1` / `MODEL_NAME_GENERATED_2`, … (multiple models).
  Normally no fallback default is written into the Python source; the one
  exception is when no `load_dotenv()` bootstrap could be installed (no
  package `__init__.py`), where the original literal is kept as the
  fallback so the lookup cannot evaluate to `None` at runtime. The
  model string is written as the value in `.env.example` with a comment
  prompting a rename.   When re-run against a recipe whose `.env.example` already has entries,
  the writer classifies each entry (skill-authored vs. user-authored,
  TODO vs. real value) and only rewrites lines it can prove it authored;
  user-authored lines are always preserved. A stale TODO (either
  skill-authored or a bare v1-era `<TODO: update-this-value>` with no
  inline comment) is upgraded in place when source can supply a real
  default.
  IMPORTANT — two hard rules the skill NEVER breaks:
  (1) USER-EDIT SAFETY. Any `.env.example` line the skill cannot prove
  it authored is USER_OWNED and is never modified. The rewriter fails
  closed on any structural ambiguity (quoted values, backslash
  continuation, duplicate declarations) — a stale TODO left in place is
  cheap; a clobbered user edit is not.
  (2) ADDITIVE-ONLY FOR PYTHON FILES. The skill never writes new
  `os.environ.setdefault(...)` bootstrap lines into any Python file.
  Pre-existing `os.environ.setdefault(...)` or
  `os.getenv("VAR", "default")` calls that the recipe author wrote by
  hand are LEFT UNTOUCHED — the skill's only writes to Python files are
  (a) the `load_dotenv()` bootstrap; (b) `# noqa: E402` on trailing
  relative imports that would otherwise trip Ruff; (c) hardcoded
  model-literal replacement.
  Use when the user wants to "extract env vars", "update .env.example",
  "add load_dotenv", "surface setdefault defaults", "upgrade stale TODOs
  in .env.example", "replace hardcoded model names", or "fix environment
  variables" in a Python recipe.
metadata:
  author: Google
  license: Apache-2.0
  version: 2.3.0

Extract Python Environment Variables

Use this skill to ensure a Python recipe properly declares and loads all

environment variables it uses.


What This Skill Does

Runs scripts/extract_env_vars.py against a recipe directory. The script:

  1. Scans all .py files (excluding tests/ and common cache/venv

directories) for environment variable accesses:

  • os.environ["VAR"]
  • os.environ.get("VAR") / os.environ.get("VAR", "default")
  • os.getenv("VAR") / os.getenv("VAR", "default")
  • os.environ.setdefault("VAR", "default") &nbsp;&nbsp;*(v2)*

Only names matching ^[A-Z_][A-Z0-9_]*$ (UPPER_SNAKE_CASE) are captured;

a lowercase name like os.getenv("my_api_key") is skipped and a

[WARN] line lists any that were dropped. Rename such vars to uppercase

in source, or add them to .env.example by hand.

  1. Updates .env.example — appends any variables not already

declared AND upgrades stale TODO entries in place. Existing

user-authored lines are always preserved (see Rule 1 below).

  • Creates .env.example from scratch if it does not exist.
  • Each new/upgraded line has the shape:

```

VAR=<value> # extracted-by:extract-env-vars; <provenance>

```

  • <value> is the resolved default extracted from source when the

author committed to one — with os.environ.setdefault(...) beating

os.getenv("V", "d") / os.environ.get("V", "d") (setdefault is a

stronger commitment: it mutates the process environment, while getenv

fallback is per-read). Alphabetical file order breaks ties.

  • Values that look like stubs are downgraded to

<TODO: update-this-value> — e.g. my-project-id, your-api-key,

changeme, <...>, anything containing example.com. The source

string is preserved in the marker comment so the maintainer sees

what was found and can fix the source too.

  • When no source supplied a string-literal default, the value is

<TODO: update-this-value> with a no default in source note.

  • Stale TODO upgrade (v2.1): if an existing entry is either a

skill-authored TODO (has the marker) or a bare v1-era TODO (exact

value <TODO: update-this-value>, no marker, no inline comment)

AND source can now supply a real default, the line is rewritten in

place. This closes the discoverability loop for recipes originally

processed by v1 (which always wrote TODOs regardless of what source

said).

  1. Injects load_dotenv() into the package __init__.py (the first

subdirectory inside the recipe that contains an __init__.py, skipping

tests/ and hidden directories). The snippet injected is:

```python

from dotenv import load_dotenv

# Load variables from .env if present. In production the environment is

# already populated by the platform (Cloud Run, GKE, etc.), so a missing

# .env is expected and not an error.

load_dotenv()

```

If load_dotenv is already present the injection is skipped.

If no package __init__.py exists (common in vertical plugins under

plugins/, where the code lives in a plain scripts/ directory rather

than an importable package) the injection is skipped with a [WARN],

and the step reports that no bootstrap is in place. Step 4 below depends

on that answer.

Additionally — always, regardless of whether we injected — appends

# noqa: E402 -- must come after load_dotenv() to any top-level relative

import (from .x import y) that sits AFTER a non-import module-level

statement. Two cases this covers:

  • Fresh injection. The injected load_dotenv() call pushes

pre-existing trailing relative imports below a non-import statement, so

they'd trigger Ruff E402 ("module-level import not at top of file")

when Phase 4 (ruff) of prepare-python-recipe runs.

  • Author-written bootstrap. The recipe author already wrote

load_dotenv() + os.environ.setdefault(...) calls followed by a

trailing from . import agent, but never marked the trailing import.

The skill did NOT inject anything (load_dotenv was already present) but

still adds the noqa suffix so the file is lint-clean on the pipeline's

next ruff pass.

The suppression pass is precise — a relative import at the very TOP of

the file (before any non-import statement) is fine and left untouched.

Idempotent: a line that already carries # noqa: E402 is skipped.

  1. Replaces hardcoded model names in source (e.g. model="gemini-3.5-flash"

in agent.py) with an os.getenv(...) call.

Position decides eligibility. A model string is only promoted when it

is a *configurable constant*. Three positions mean it is something else,

and are left untouched:

  • Collection-literal entries (dict keys and values, list/set/tuple

items) — lookup tables and enumerations of supported models:

```python

IMAGE_MODELS = {

"flash": "gemini-2.5-flash-image",

"pro": "gemini-2.5-pro-image",

}

```

Rewriting the values collapses the table onto one env var; rewriting the

keys is worse, because IMAGE_MODELS.get("flash") then never

matches anything. A dict key must stay a static literal.

  • Subscript indices — IMAGE_MODELS["gemini-3.1"] is a key *into*

such a table, so replacing it looks up a different entry.

  • Comparison operands — if "gemini-3.1" in model_id tests a value

rather than configuring one.

Skipping a legitimate extraction is cheap (lift it by hand); silently

breaking a lookup table is not, so this errs towards skipping. Every

skipped literal is listed in an [INFO] block, so nothing is hidden — and

because these strings never reach the naming step, a dict target like

IMAGE_MODELS can no longer leak into .env.example as a variable name.

Variable name. The assignment target that holds the literal is used

when it names a model, since it carries far more meaning than a generic

fallback — DEFAULT_EMBEDDING_MODEL = "gemini-embedding-001" and

embedding_model = cfg.get("embedding_model", "gemini-embedding-001")

both yield EMBEDDING_MODEL. This matters in recipes that already read a

*different* model var (say GEMINI_MODEL for the LLM), where a second

bare MODEL_NAME would be actively misleading. When a string is assigned

to conflicting target names, the most frequent wins, ties broken

alphabetically. Otherwise:

  • A single unnamed model → MODEL_NAME
  • The rest → MODEL_NAME_GENERATED_1, MODEL_NAME_GENERATED_2, … (sorted

alphabetically for determinism)

Fallback argument. Normally the emitted call is bare —

os.getenv("EMBEDDING_MODEL"), no default — because default values are

the maintainer's decision, not the skill's. That is only safe when the

load_dotenv() bootstrap from step 3 is in place to populate the

environment.

When step 3 could not install a bootstrap (no package __init__.py),

nothing reads .env, so a bare lookup would evaluate to None at runtime

and silently break the recipe. In that case the original literal is

preserved as the fallback — `os.getenv("EMBEDDING_MODEL",

"gemini-embedding-001")` — which keeps behaviour identical to before the

rewrite while still lifting the value into the environment. The step logs

an [INFO] line explaining the choice.

The actual model string is written as the value in .env.example (e.g.

EMBEDDING_MODEL=gemini-embedding-001) with a comment prompting the

maintainer to rename the variable if the derived name isn't right.

  1. Updates pyproject.toml — adds python-dotenv>=1.0.0 to [project]

dependencies if it is not already there.


Hard rules the skill NEVER breaks

Rule 1 — User-edit safety for .env.example. The writer classifies

every existing entry before touching anything, and only rewrites lines

it can prove it authored. Concretely:

Existing entry looks likeSource now providesAction
Missinganyappend with resolved default (or TODO)
A line the skill wrote (has # extracted-by:extract-env-vars marker) with <TODO: update-this-value> valuea REAL default (not placeholder-shape)upgrade in place
Same as aboveno default OR a placeholder-shape defaultskip (would be TODO → TODO churn)
Bare VAR=<TODO: update-this-value> line with no marker AND no inline comment — a v1-era TODOa REAL defaultupgrade in place (v1 → v2 migration)
A line the skill wrote with a REAL valuesource has any value (even different)skip (never overwrite skill-authored values on drift)
Anything else (user typed value, user-authored comment, non-standard formatting)anyskip — never touched

Golden rule underneath all of this: fail closed. If the classifier

can't confidently prove a line was skill-authored or is a bare v1 TODO,

it's USER_OWNED and untouchable. If the rewriter finds a line whose

structure it doesn't understand (quoted value, backslash continuation,

duplicate declarations), it refuses and warns rather than guessing. And

before any write, the assembled file is re-parsed to verify every

planned upgrade re-parses as a skill-authored real value — a bug in the

rewriter cannot silently corrupt .env.example.

To force regeneration of a line the skill would otherwise skip, delete

the line and re-run.

*Rationale for extracting defaults (v2) and upgrading stale TODOs

(v2.1): a value hidden inside a Python file that a user must know about

but has no reason to look at is, in practice, undocumented.

.env.example is the documented configuration surface. Surfacing what

the author already committed to in code — and updating a stale TODO

once source can supply a real default — makes the configuration

discoverable without inventing anything new. The value in source WAS

the value; we're only lifting it into view.*

Rule 2 — Additive-only for Python files. The skill never writes new

os.environ.setdefault(...) bootstrap lines into any Python file.

Pre-existing os.environ.setdefault(...) or

os.getenv("VAR", "default") calls that a recipe author wrote by hand

are LEFT UNTOUCHED. The skill's only writes to Python files are:

  • Adding the from dotenv import load_dotenv + load_dotenv() snippet

(once, only if not already present).

  • Appending # noqa: E402 to trailing relative imports that would

otherwise trip Ruff after the env-bootstrap block.

  • Replacing hardcoded model literals with os.getenv(...) calls — bare

when a load_dotenv() bootstrap is in place, otherwise retaining the

original literal as the fallback (see step 4 above).

Note that scanning os.environ.setdefault(...) and lifting its value

into .env.example (v2) does NOT violate Rule 2 — the skill READS from

Python source (unchanged behaviour) and WRITES only to .env.example.


Rules for the Agent

  1. Always use the script — never manually edit .env.example, __init__.py,

or pyproject.toml to perform these changes.

  1. Ask for the recipe directory if the user has not provided one. Do not

assume a path.

  1. After the script succeeds, remind the user to:
  • Copy .env.example → .env and fill in real values before running locally.
  • Run uv sync to pick up the python-dotenv dependency if it was newly added.

Input

FieldRequiredDescription
Recipe directoryYesPath to the recipe root (e.g. contrib/python/my-recipe, core/python/my-recipe, or plugins/retail/store-ops)

If the user has not specified the recipe directory, ask for it before proceeding.


Run

Run it through uv so it always executes on a Python 3.11+ interpreter — the

script uses the stdlib tomllib, which only exists from 3.11 onward. A bare

python3 that resolves to 3.9/3.10 fails with ModuleNotFoundError: tomllib.

No --with packages are needed (the script is stdlib-only).

uv run --no-project python3 \
  .agents/skills/extract-python-environment-variables/scripts/extract_env_vars.py \
  --recipe-dir <RECIPE_DIR>

Preview first (optional)

Add --dry-run to report exactly what *would* change without modifying any

files. Nothing is written to .env.example, __init__.py, pyproject.toml, or

any source file. Useful for inspecting a recipe before committing to the edits:

uv run --no-project python3 \
  .agents/skills/extract-python-environment-variables/scripts/extract_env_vars.py \
  --recipe-dir <RECIPE_DIR> --dry-run

In dry-run output, actions are prefixed with [DRY-RUN] and phrased as

"Would add" / "Would inject" / "Would replace".


Respond

Do not show the script's raw stdout. Reformat its results into clear Markdown

tables (variables to add, files to update, model replacements) so they are easy

to read — this is especially important for --dry-run output. For each variable

or model string, include a column with the source file where it was found

(locate it in the recipe's Python source; ignore .env and .env.example).

For added variables, surface the resolved default and its provenance

so the maintainer knows which value the skill chose and where it came from:

VariableValue written to .env.exampleSource
GOOGLE_CLOUD_PROJECT<TODO: update-this-value> *(downgraded — source had "my-project-id")*agent.py (setdefault)
GOOGLE_GENAI_USE_VERTEXAITRUE__init__.py (setdefault)
LOG_LEVELINFOlogging_setup.py (getenv fallback)
API_KEY<TODO: update-this-value> *(no default in source)*client.py (getenv, no fallback)

If the script reports upgrades (stale TODO entries rewritten in

place with real defaults from source), surface those in a separate table

so the maintainer can eyeball each one:

VariableWasNowSource
USE_VERTEX<TODO: update-this-value>TRUE__init__.py (setdefault)

If the script logged [WARN] Refused to upgrade … for any variable,

surface that too — the line's structure was ambiguous and was left

untouched deliberately.

Once the script finishes successfully, summarise what changed:

  • Which variables were added to .env.example (with resolved value +

source) or confirm it was already up to date.

  • Which existing TODOs were upgraded in place (v2.1) with real

defaults from source.

  • Any values that were **downgraded to <TODO> because they looked like

placeholders** — the maintainer should fix the source too.

  • Whether load_dotenv() was injected or was already present.
  • Whether python-dotenv was added to pyproject.toml or was already there.

Then remind the user of the next steps:

Next steps:
  cp <RECIPE_DIR>/.env.example <RECIPE_DIR>/.env   # then fill in real values
  cd <RECIPE_DIR> && uv sync                        # install python-dotenv if newly added

Do not make any further changes. End your turn.

Related

SkillSoftware engineering

claude-api

Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration. TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`,…

Sourcegithub.com/anthropics/skills/blob/main/skills/claude-api/SKILL.md

No credentialsDoes not sendApache-2.0
SkillSoftware engineering

doc-coauthoring

Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar…

Sourcegithub.com/anthropics/skills/blob/main/skills/doc-coauthoring/SKILL.md

No credentialsDoes not sendnot stated
SkillSoftware engineering

mcp-builder

Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).

Sourcegithub.com/anthropics/skills/blob/main/skills/mcp-builder/SKILL.md

No credentialsDoes not sendApache-2.0
SkillSoftware engineering

skill-creator

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.

Sourcegithub.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md

No credentialsDoes not sendApache-2.0
SkillSoftware engineering

webapp-testing

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

Sourcegithub.com/anthropics/skills/blob/main/skills/webapp-testing/SKILL.md

No credentialsDoes not sendApache-2.0
SkillSoftware engineering

ai-debt-detector

Use after generating code, after accepting AI suggestions, or when reviewing AI-written modules. Also use when code works but feels brittle, when error handling seems thin, when orphaned resources or missing cleanup are suspected, or when the agent claims done but hidden debt may exist. Catches the specific failure patterns AI agents produce that humans would not.

Sourcegithub.com/wshobson/agents/blob/main/plugins/skill-forge-essentials/skills/ai-debt-detector/SKILL.md

No credentialsDoes not sendMIT