Agent skill

cogapp-markdown

Use cogapp to auto-generate sections of markdown documentation by embedding Python code that produces content. Use when a project needs to keep documentation in sync with code, such as embedding CLI --help output in README files, generating tables, or any content that should be derived from the code itself rather than manually maintained.

Stars 40
Forks 5

Install this agent skill to your Project

npx add-skill https://github.com/simonw/skills/tree/main/cogapp-markdown

SKILL.md

cogapp for Markdown Documentation

Use cogapp to auto-generate sections of markdown files. Cog lets you embed Python code in markdown that produces output inline — the generated content lives in the file alongside the code that created it. Running cog -r regenerates the output, keeping docs in sync with code.

Install

bash
pip install cogapp

Or add "cogapp" to your [dependency-groups] dev dependencies in pyproject.toml.

Markdown Syntax

Use HTML comments as cog markers so the Python code is invisible when the markdown is rendered:

markdown
<!-- [[[cog
import cog
cog.outl("This content is generated by cog.")
]]] -->
This content is generated by cog.
<!-- [[[end]]] -->

The pattern is:

  1. <!-- [[[cog — opens the Python code block (hidden in rendered markdown)
  2. Python code that calls cog.out() or cog.outl() to produce output
  3. ]]] --> — closes the Python code block
  4. Generated output appears here (visible in rendered markdown)
  5. <!-- [[[end]]] --> — marks the end of the generated region

Running Cog

Regenerate all cog blocks in-place:

bash
cog -r docs/*.md

Or without installing — use uv run --with to run cog in a temporary environment:

bash
uv run --with cogapp cog -r docs/*.md

The -r flag replaces file contents in-place. Without it, cog writes to stdout.

Key Pattern: Embedding CLI --help Output

The most common use is keeping CLI documentation in sync with actual --help output.

For Click-based CLIs, use CliRunner to capture help output directly (no subprocess needed):

markdown
<!-- [[[cog
import cog
from mypackage import cli
from click.testing import CliRunner
runner = CliRunner()
result = runner.invoke(cli.cli, ["mycommand", "--help"])
help = result.output.replace("Usage: cli", "Usage: mypackage")
cog.out(
    "```\n{}\n```\n".format(help.strip())
)
]]] -->
<!-- [[[end]]] -->

The replace("Usage: cli", "Usage: mypackage") is needed because CliRunner reports the command name as cli instead of the real entry point name.

For argparse or other CLIs, use subprocess:

markdown
<!-- [[[cog
import cog
import subprocess
result = subprocess.run(["mycommand", "--help"], capture_output=True, text=True)
cog.out(
    "```\n{}\n```\n".format(result.stdout.strip())
)
]]] -->
<!-- [[[end]]] -->

GitHub Actions: Fail CI if Cog Hasn't Been Run

Add a step to your test workflow that checks all cog blocks are up to date. This fails CI if someone changes CLI behavior but forgets to regenerate the docs:

yaml
- name: Check if cog needs to be run
  run: |
    cog --check docs/*.md

cog --check exits with code 1 if any file would change. It does not modify files.

Use --check-fail-msg to tell developers how to fix it:

yaml
- name: Check if cog needs to be run
  run: |
    cog --check --check-fail-msg='Run "cog -r docs/*.md" to update' docs/*.md

If the project uses uv and cogapp is not a declared dependency, use uv run --with:

yaml
- name: Check if cog needs to be run
  run: |
    uv run --with cogapp cog --check docs/*.md

Other Patterns

Generating a Markdown Table

markdown
<!-- [[[cog
import cog
headers = ["Name", "Type", "Description"]
rows = [
    ["id", "int", "Primary key"],
    ["name", "str", "User name"],
]
cog.outl("| " + " | ".join(headers) + " |")
cog.outl("| " + " | ".join(["---"] * len(headers)) + " |")
for row in rows:
    cog.outl("| " + " | ".join(row) + " |")
]]] -->
<!-- [[[end]]] -->

Generating a Code Block

markdown
<!-- [[[cog
import cog
import json
data = {"key": "value", "count": 42}
cog.out("```json\n")
cog.outl(json.dumps(data, indent=2))
cog.out("```\n")
]]] -->
<!-- [[[end]]] -->

Multiple Blocks in One File

Each cog block is independent. You can have many blocks in one file — each gets its own <!-- [[[cog ... ]]] --> and <!-- [[[end]]] --> pair. Imports are shared across blocks within the same file.

Useful Flags

  • cog -r FILE — regenerate in-place
  • cog --check FILE — check without modifying (for CI)
  • cog --check --diff FILE — show what would change
  • cog -P FILE — use print() instead of cog.outl() in code blocks

Didn't find tool you were looking for?

Be as detailed as possible for better results