framework/core/skills/flowai-skill-setup-ai-ide-devcontainer/SKILL.md
Set up .devcontainer for AI IDE development. Generates devcontainer.json and optional Dockerfile for project tech stack, AI CLI integration (Claude Code, OpenCode, flowai), skill mounting, and security hardening.
npx skillsauth add korchasa/flow flowai-skill-setup-ai-ide-devcontainerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Creates a .devcontainer/ configuration for AI-agent-driven development.
Architecture: VS Code or Cursor opens the devcontainer (they support the devcontainer spec natively). AI tools work inside the container in two modes:
anthropic.claude-code, github.copilot) — installed automatically via customizations.vscode.extensions, share the same container config and env varsclaude CLI, opencode CLI) — run in the container terminal, use the same ~/.claude/ or ~/.config/opencode/ configBoth modes share config directories, env vars, and global skills. This skill configures all layers.
All authentication is manual. The skill MUST NOT generate:
remoteEnv auth vars. Never forward ANTHROPIC_API_KEY, GITHUB_TOKEN, CLAUDE_CONFIG_DIR via ${localEnv:...}. Unset ANTHROPIC_API_KEY → empty string → Claude CLI enters API-key mode and silently breaks OAuth. CLAUDE_CONFIG_DIR redirects .credentials.json off the volume. GITHUB_TOKEN has no consumer. Non-auth vars (NODE_ENV, DENO_DIR) are fine when actually needed.secrets block. Codespaces metadata for prompting credentials — nothing here consumes them.initializeCommand. The Keychain-extraction forwarder was removed (macOS-only, undocumented format, split-brain refresh).gh auth login, claude login, opencode auth login, or any credential copy into .credentials.json.setup-container.sh does exactly ONE thing: recursive chown -R of writable volumes so the user's manual *login commands can write.
Persistence: Docker named volumes with stable names source=${localWorkspaceFolderBasename}-<purpose>,target=<path>,type=volume. Never ${devcontainerId} — it rehashes on every edit and orphans the volume.
Host config visibility (optional, local dev only): read-only bind mount of host ~/.claude/ onto container ~/.claude-host/ (or ~/.config/opencode-host/ for OpenCode) — always a separate target path, never over the writable volume. Read-only is mandatory — a RW mount would also cause split-brain token refresh (host CLI and container CLI refreshing the same refreshToken independently; whichever refreshes second is invalidated). This is data visibility, not auth forwarding — on macOS the host dir has no .credentials.json anyway (tokens live in Keychain).
Every verification, reference table, and post-setup note below derives from this section. Do not restate — point here.
package.json, deno.json, go.mod, Cargo.toml, pyproject.toml, or similar)Scan the project root for stack manifests and apply the priority below (top-down, stop at first match). tsconfig.json is NOT a primary indicator — it accompanies a primary manifest.
deno.json / deno.jsonc → Deno (ignore any tsconfig.json; commonly present for LSP interop only) — base mcr.microsoft.com/devcontainers/base:ubuntu + Deno featurego.mod → Go — base mcr.microsoft.com/devcontainers/goCargo.toml → Rust — base mcr.microsoft.com/devcontainers/rustpyproject.toml / requirements.txt / setup.py → Python — base mcr.microsoft.com/devcontainers/pythonpackage.json → Node/TS — base mcr.microsoft.com/devcontainers/typescript-nodetsconfig.json with none of the above → Generic + ask user. Do NOT assume Node.mcr.microsoft.com/devcontainers/base:ubuntuIf MULTIPLE top-level manifests match (e.g. package.json AND go.mod), ask the user which is primary. Secondary stacks become features.
Scan the project for indicators that map to devcontainer features beyond the base stack. Use the indicator→need mapping in references/features-catalog.md, then search https://containers.dev/features for matching feature IDs.
pnpm-lock.yaml → need pnpm, *.tf → need Terraform)features block in step 5 (Generate Configuration)Skip this step only if user explicitly provided a complete feature list in their request.
Check if .devcontainer/ exists:
devcontainer.json and display it to the user.Ask the user (skip items already answered in prior context):
<cli> login inside the container after first start)"
postCreateCommand script (curl claude.ai/install.sh) + writable named volume for ~/.claudeghcr.io/jsburckhardt/devcontainer-features/opencode:1, preferred) or curl opencode.ai/install in postCreateCommand + writable named volume for ~/.config/opencodedeno install in postCreateCommand (requires Deno runtime; auto-added as feature for non-Deno stacks)*-host pathNET_ADMIN+NET_RAW Linux capabilities (needed to program iptables), which is a meaningful privilege increase — weigh this against the egress-control benefit."
init-firewall.sh, adds NET_ADMIN/NET_RAW capabilitiesGenerate using the template logic in references/devcontainer-template.md.
Key structure (the rules in Auth Policy above apply — do not generate any remoteEnv/secrets/initializeCommand):
{
"name": "<project-name>",
"image": "<base-image>", // OR "build": { "dockerfile": "Dockerfile" } — see 5.2
"features": { /* stack features + common-utils + github-cli */ },
"customizations": {
"vscode": {
"extensions": [ /* stack extensions + AI extensions */ ],
"settings": { /* stack-specific settings */ }
}
},
"mounts": [
// Writable named volume for container's own state
// Read-only bind mount of host ~/.claude → ~/.claude-host (separate path) — if host data visibility enabled
],
// Object form runs entries in parallel. setup-container.sh is a self-healing
// chown guard — no ordering dependency with other entries.
"postCreateCommand": {
"deps": "<dependency-install-command>",
"setup": ".devcontainer/setup-container.sh",
"claude-cli": "curl -fsSL https://claude.ai/install.sh | bash"
},
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"remoteUser": "<non-root-user>"
}
Generate only when the user chose custom Dockerfile in Step 4, item 4. See references/dockerfile-patterns.md.
Generate only when the user chose firewall in Step 4, item 3. See references/firewall-template.md.
.devcontainer/ directory if missing.devcontainer/devcontainer.json.devcontainer/Dockerfile (if custom).devcontainer/init-firewall.sh (if firewall), make executable.devcontainer/setup-container.sh (only when at least one of ~/.claude, ~/.config/opencode, /commandhistory exists as a writable volume; skip entirely otherwise), make executable. The script's sole responsibility is a recursive self-healing chown of those volumes so the user's manual claude login / gh auth login / opencode auth login commands can write to them. It does NOT authenticate anything itself. See references/devcontainer-template.md § setup-container.shStructural:
.devcontainer/devcontainer.json parses via a JSONC parser (comments and trailing commas are allowed)FROM line presentinit-firewall.sh exists: has shebang and set -euo pipefailsetup-container.sh exists: has shebang, set -euo pipefail, is executableremoteUser matches the base image (e.g. node for Node images, vscode for mcr images, deno for denoland images)Auth Policy compliance (see the Auth Policy section above for rationale):
remoteEnv block (or only non-auth vars like NODE_ENV)secrets blockinitializeCommandsetup-container.sh body is strictly a chown loop — no gh auth, no claude login, no cp into .credentials.json${localWorkspaceFolderBasename}-*, not ${devcontainerId}-*~/.claude is mounted read-only at ~/.claude-host (separate path), NOT at ~/.claudeEnd-to-end (when devcontainer CLI is available):
devcontainer up --workspace-folder . exits 0 with outcome:successdevcontainer exec --workspace-folder . bash -lc 'claude --version && ls ~/.claude-host/ && touch ~/.claude/.perm-test && rm ~/.claude/.perm-test' succeeds. Do NOT expect claude auth status to show authenticated — claude login is the user's manual step.After generation, show the user the one-time manual auth steps (because of the Auth Policy above, the skill does nothing automatic):
General: tokens live in writable named volumes with stable names (${localWorkspaceFolderBasename}-*) and survive restarts, rebuilds, and devcontainer.json edits. Renaming the workspace folder changes the basename and therefore the volume — you will re-auth.
If Claude Code was selected:
Open a terminal inside the container and run
claude login. OAuth opens in a browser via IDE URL forwarding. Credentials are written to~/.claude/.credentials.jsonin volume${localWorkspaceFolderBasename}-claude-config.Host data is mounted read-only at
~/.claude-host/(separate from the writable~/.claude/). The agent can read:projects/<workspace-hash>/*.jsonl,history.jsonl,sessions/,skills/,commands/.
If OpenCode was selected:
Run
opencode auth login(or the provider-specific variant) in the container terminal. State persists in volume${localWorkspaceFolderBasename}-opencode-config.
Always (the github-cli feature is always included):
Run
gh auth loginonce inside the container — choose GitHub.com → HTTPS → "Login with a web browser" (this is GitHub's OAuth web-browser flow; copy the one-time code it displays into the browser). This authenticates theghCLI and registers it as the git credential helper for HTTPS remotes in one step.SSH vs HTTPS:
gh auth logindoes not affect SSH remotes. If the repo was cloned as[email protected]:..., SSH operations depend on VS Code / Cursor's SSH agent forwarding. If forwarding is unavailable: eithergit remote set-url origin https://github.com/<owner>/<repo>.gitand re-rungh auth login, or configure SSH keys in the container manually.
If flowai was selected:
flowaiis installed globally via Deno. Runflowai syncin the container terminal to sync skills/agents..flowai.yamlis read from the project workspace root.
| Stack | Features to Add |
|---|---|
| Deno | ghcr.io/devcontainers-extra/features/deno:latest |
| Node/TS | (included in base image) |
| Python | (included in base image) |
| Go | (included in base image) |
| Rust | (included in base image) |
| Common (always) | ghcr.io/devcontainers/features/common-utils:2, ghcr.io/devcontainers/features/github-cli:1 |
| Secondary Node | ghcr.io/devcontainers/features/node:1 (when Node needed alongside non-Node primary) |
| Discovered | Additional features from references/features-catalog.md based on project scan (Step 2) |
| Stack | Extensions |
|---|---|
| Deno | denoland.vscode-deno |
| Node/TS | dbaeumer.vscode-eslint, esbenp.prettier-vscode |
| Python | ms-python.python, ms-python.vscode-pylance |
| Go | golang.go |
| Rust | rust-lang.rust-analyzer |
| Common (always) | eamodio.gitlens, editorconfig.editorconfig |
| Tool | Extension ID | Notes |
|---|---|---|
| Claude Code | anthropic.claude-code | IDE extension + CLI inside container |
| GitHub Copilot | github.copilot, github.copilot-chat | IDE extension only |
OpenCode is a standalone TUI/CLI — no VS Code extension. It runs in the container terminal.
| Stack | Command |
|---|---|
| Deno | deno install or deno cache (check deno.json for deps) |
| Node/TS | npm install or yarn install or pnpm install (match lockfile) |
| Python | pip install -r requirements.txt or pip install -e . (match project) |
| Go | go mod download |
| Rust | cargo fetch |
| Base Image Pattern | remoteUser |
|---|---|
| mcr.microsoft.com/devcontainers/* | vscode |
| node:* | node |
| denoland/deno:* | deno |
| debian:* / ubuntu:* | Create non-root user in Dockerfile |
Per-tool specifics only. All auth/env/secrets rules come from the Auth Policy section above — they are NOT restated here.
Installation preference is per-tool — see each subsection below. Rule of thumb: use the official install script in postCreateCommand when the registry feature is known to ship outdated or broken binaries (Claude Code); use the registry feature when it is maintained and up-to-date (OpenCode, Cursor CLI, Gemini CLI). See references/features-catalog.md for the full matrix.
curl -fsSL https://claude.ai/install.sh | bash in postCreateCommand. Alternative: npm install -g @anthropic-ai/claude-code@latest.source=${localWorkspaceFolderBasename}-claude-config,target=/home/<user>/.claude,type=volume. Claude CLI writes .credentials.json here after claude login. ~/.claude.json in home root is metadata/cache only and is auto-recreated.source=${localEnv:HOME}/.claude,target=/home/<user>/.claude-host,type=bind,readonly. Exposes projects/, sessions/, history.jsonl, skills/, commands/ to the agent.postStartCommand): rm -rf ~/.claude/skills ~/.claude/commands && cp -rL ~/.claude-host/skills ~/.claude/skills 2>/dev/null || true && cp -rL ~/.claude-host/commands ~/.claude/commands 2>/dev/null || true. Use cp -rL (dereference symlinks) since host skills may be symlinks with host-relative paths. This is NOT a required step — agents can also read directly from ~/.claude-host/.anthropic.claude-code.ghcr.io/jsburckhardt/devcontainer-features/opencode:1. Fallback: curl -fsSL https://opencode.ai/install | bash in postCreateCommand.source=${localWorkspaceFolderBasename}-opencode-config,target=/home/<user>/.config/opencode,type=volume.source=${localEnv:HOME}/.config/opencode,target=/home/<user>/.config/opencode-host,type=bind,readonly.postStartCommand): rm -rf ~/.config/opencode/skills && cp -rL ~/.config/opencode-host/skills ~/.config/opencode/skills 2>/dev/null || true.ghcr.io/stu-bell/devcontainer-features/cursor-cli:0.deno install -g -A -f jsr:@korchasa/flowai in postCreateCommand. Requires Deno; for non-Deno stacks add ghcr.io/devcontainers-extra/features/deno:latest to features..flowai.yaml from the project workspace.Host bind mounts to $HOME do NOT work in GitHub Codespaces. For Codespaces, drop the bind mount entirely — the user runs claude login / opencode auth login inside the Codespace, and the agent has no host data visibility (there is no "host" in Codespaces).
| Hook | When | Use For |
|---|---|---|
| initializeCommand | On host, before container creation | NOT used by this skill (Auth Policy forbids it) |
| postCreateCommand | Once after container creation | Dependency install, CLI installers, setup-container.sh (chown-only) |
| postStartCommand | Every container start | git safe.directory, optional host skills sync (cp -rL ~/.claude-host/skills ~/.claude/skills) |
| postAttachCommand | Every IDE attach | Shell customization |
All hooks accept string, array, or object (parallel execution) format:
// Object form for parallel execution
"postCreateCommand": {
"deps": "npm install",
"cli": "curl -fsSL https://claude.ai/install.sh | bash"
}
development
Use when the user asks to add TypeScript strict-mode code-style rules to AGENTS.md for a TypeScript project using strict mode. Do NOT trigger for Deno projects (use setup-agent-code-style-deno) or non-strict TS configurations.
development
Use when the user asks to add Deno/TypeScript code-style rules to AGENTS.md, or during initial Deno project setup when code-style guidelines need to be established. Do NOT trigger for non-Deno TypeScript projects (use setup-agent-code-style-strict), or for runtime-agnostic style advice.
testing
Use when the user provides a source (URL, file path, or free text) to save into the project's memex — a long-term knowledge bank for AI agents. Stores the raw source, extracts entities into cross-linked pages, runs a backlink audit, and updates the index and activity log. Do NOT trigger on casual reads; only when the intent is to persist a source into the memex.
development
Use when the user asks to audit a memex (long-term knowledge bank for AI agents) for orphans, dead SALP REFs, missing sections, contradictions, or index drift. Runs a deterministic structural check, layers LLM-judgement findings, optionally auto-fixes trivial issues with `--fix`. Do NOT trigger on general code linting.