Two expensive build inputs can outlive an ephemeral CI runner without becoming
part of the deployed site: Content's downloaded public repositories and the
rendered OG-image cache. They have different invalidation rules, so duxt exposes
one command for each instead of sharing a key.

## Cache downloaded public sources

`duxt-cache-key` loads the resolved source list, after `latest` and release
selection have become concrete refs, and emits the exact cache path and key.

```yaml [.github/workflows/deploy.yml]
- name: Resolve the remote-source cache key
  id: sources
  run: duxt-cache-key --root . --github >> "$GITHUB_OUTPUT"

- name: Restore the downloaded sources
  if: steps.sources.outputs.cacheable == 'true'
  uses: actions/cache@v6
  with:
    path: ${{ steps.sources.outputs.paths }}
    key: ${{ steps.sources.outputs.key }}
```

Use the directory containing the consuming Nuxt app as `--root`; use `.` when
the app is the repository root. Run the command after installing dependencies
and before the Nuxt build.

The emitted paths include Content's downloaded checkouts and exclude
`contents.sqlite*`. That database is a parse result, not a download: restoring
it could hide an authored content change behind stale parsed rows.

The cache is **public sources only**. A cache produced on a protected branch can
be restored by a pull request, so one private, SSH or credential-bearing remote
sets `cacheable=false` for the whole list. The workflow then skips the cache;
duxt never mixes an unsafe remote with an otherwise public cache.

Run the command without `--github` to see the redacted inputs and the reason a
site is or is not cacheable:

```bash
duxt-cache-key --root .
```

## Cache rendered OG images

`duxt-og-cache` emits a rolling Actions key. The renderer dependency versions
form its namespace, while the source revision lets a successful build save the
new images it added instead of freezing the first exact hit forever.

```yaml [.github/workflows/deploy.yml]
- name: Resolve the OG image cache key
  id: og
  run: duxt-og-cache --root . --run "$GITHUB_SHA" --github >> "$GITHUB_OUTPUT"

- name: Restore the rendered OG images
  uses: actions/cache@v6
  with:
    path: ${{ steps.og.outputs.path }}
    key: ${{ steps.og.outputs.key }}
    restore-keys: ${{ steps.og.outputs['restore-keys'] }}
```

The consuming site's Nuxt config must also register the OG cache integration
exported by `@kirchdev/duxt/og-image-cache`. That build-side fingerprint covers
the site-owned inputs the pre-build CLI cannot safely load, such as template
options and fonts. When they move, the integration empties the restored
directory before any stale image can be served.

After the build, report what the cache actually reused:

```bash
duxt-og-cache --root . --report --since "$STARTED_MS" --log build.log
```

Add `--github` when the report is written to `$GITHUB_OUTPUT`; its plain form is
suited to a terminal or `$GITHUB_STEP_SUMMARY`.

## Checklist

- [ ] Dependencies are installed before either key command runs.
- [ ] `--root` names the consuming Nuxt application.
- [ ] The source cache step is conditional on `cacheable == 'true'`.
- [ ] The source cache uses no broad restore key.
- [ ] `contents.sqlite*` remains excluded from the source cache.
- [ ] No private or authenticated source is forced into an Actions cache.
- [ ] The OG cache uses the emitted rolling restore keys.
- [ ] The consuming Nuxt config fingerprints site-owned rendering inputs.
- [ ] The post-build report uses the same cache directory as the build.