gen-devcontainer
Wraps bin/gen-devcontainer.sh. Emits a devcontainer.json; preview-
by-default; idempotent on apply.
When to trigger
- User wants a Codespaces-ready repo and there's no
.devcontainer/directory yet. - User wants to refresh an existing devcontainer.json after upgrading
nyann (base image tags + extension lists shift with the
snapshot_versioncomment in the file). - User wants to add forwarded ports / cpus / memory to an existing
devcontainer (use
--port 3000 --cpus 4 --memory 8gb).
When NOT to trigger
- User wants a production Dockerfile — that's an entirely different concern; devcontainer.json is dev-environment only.
- User wants to debug a broken devcontainer build — open the existing file with them; this skill generates fresh, not diagnoses.
- User is on a non-Codespaces, non-VS Code editor — the file still
works under JetBrains' Dev Containers plugin and
devcontainer open(CLI), but flag the broader VS Code-first defaults.
Picking the language
Read StackDescriptor.primary_language from bin/detect-stack.sh:
| StackDescriptor.primary_language | --language |
|---|---|
typescript / javascript |
node |
python |
python |
go |
go |
rust |
rust |
dart |
dart |
java / kotlin |
java |
csharp / dotnet |
dotnet |
php |
php |
ruby |
ruby |
swift |
swift |
elixir |
elixir |
cpp / c |
cpp |
For polyglot monorepos (workspaces[] present), pick the language of
the most-important workspace OR ask the user — running multiple
devcontainers from a single repo is uncommon and confusing.
Preview, then apply
Always preview first. Show the rendered JSON; wait for confirmation:
bin/gen-devcontainer.sh --language python --name my-app
Then on confirmation:
bin/gen-devcontainer.sh --language python --name my-app \
--target . --apply
If the destination already matches: log "unchanged" and exit. If it
differs: print the unified diff to stderr, exit 3, prompt the user
before re-running with --force-overwrite.
Output shape
.devcontainer/devcontainer.json containing:
$schemapointing at the official devcontainer spec$commenttagging the nyann snapshot versionname(display name in 'Open in Container' picker)image(per-language, pinned to a major:minor tag)features(gh CLI, git-lfs, common-utils — version-pinned)customizations.vscode.extensions(per-language pack + gitlens + git-graph)postCreateCommand(per-language dep install —npm ci,uv sync,cargo fetch, etc.)forwardPorts(when--portflag(s) supplied)hostRequirements(when--cpus/--memory/--storagesupplied)
Defaults explained
- Base image: Microsoft devcontainers/ images where they exist (well-maintained, security-scanned, multi-arch). Vendor images for Dart / Swift / Elixir where no MS variant exists.
- Image tag: pinned to
major:minor, NOT a digest. Operators who need byte-reproducible builds should pin to a digest by hand; the Docker ecosystem ofgen-dependency-updatercan roll the tag. - Extensions: per-language language pack +
gitlens+git-graphbaseline. Conservative pick; extend via--extension. - Features:
github-cli:1+git-lfs:1+common-utils:2, always with a version pin so floating tags can't quietly re-broadcast. - postCreateCommand: best-effort install that no-ops when the
expected manifest is missing (so the file works in a partially-
scaffolded repo). Override with
--post-create-commandfor non- standard package managers (e.g. pnpm rather than npm).
Reading the output back
When previewing:
- Surface the base image (the operator may want to swap to an org-internal mirror).
- Surface the postCreateCommand (so they can spot install commands that don't fit their workflow).
- Suggest
--portflags if the user mentioned a dev-server port in the conversation.
When applying:
- Confirm the dest path didn't already exist (or that the diff was
intentional if
--force-overwritewas used). - Suggest a follow-up
gh codespace createinvocation if the user asked for Codespaces specifically.