Repository navigation
Conversation
Collapse the four devcontainer profiles into one environment at the repo root, promoting the playwright-cli-claude-codex-gemini-python profile (the only one in real use). Its setup.sh becomes the root setup.sh; the other three profiles and the near-duplicate root config are removed. The repo's payload is now the .devcontainer/ folder alone, which is self-contained and can be copied into any project. Removals: - All four profile folders (79 tracked files -> 5) - MCP-era agents and slash commands, which declared mcp__playwright__ tools that a CLI-based container never provides - Vendored social-seo skill; it is byte-identical to its upstream at zeveck/social-seo-skill, which the README now links instead - .claude/settings.local.json, which is the personal-override slot and does not belong in a template - The stale-MCP cleanup in setup.sh. It ran `rm -f .mcp.json` in the workspace root, so dropping this folder into a project that uses MCP would have silently deleted the user's own config. Browser install fixed: Setup installed Chromium twice from two sources pinning different revisions -- stable playwright (1234) and @playwright/cli's bundled playwright-core (1224) -- and only 1224 is ever launched. PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 did not prevent the extra download; that variable is only read by the npm-postinstall path, not the explicit `install` command. Consolidated into a single `playwright-cli install-browser chromium --with-deps`, which fetches the pinned revision plus the headless shell, ffmpeg, and OS-level packages. Saves roughly 650 MB per build. Also: generated artifacts (.playwright/, the playwright-cli skill) are now gitignored rather than committed, ending the manual upstream-sync commits; container renamed to "Agentic Dev Environment"; README rewritten around the drop-in workflow, with several claims corrected against the actual scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A wrapper around @devcontainers/cli so the same container can be driven from the terminal: dc up, dc shell, dc claude, dc down, and so on. Design: - All logic lives in .devcontainer/dc.mjs. The platform entry points (dc, dc.cmd) are one-line shims, so there is no duplicated logic to drift. Requiring @devcontainers/cli already guarantees Node on the host, so a single Node implementation costs nothing. - One Windows file, not two. CMD cannot execute a .ps1 at all, so a PowerShell-only install would break CMD users; dc.cmd works in CMD and resolves from PowerShell via PATHEXT. Verified locally that PowerShell resolves a bare name to a script on PATH and forwards arguments intact. - The installed copy is project-agnostic: it walks up from the cwd to find .devcontainer/devcontainer.json, so it works in any project. - dc install generates shims containing an absolute path, avoiding symlink resolution (BSD/macOS readlink lacks -f on older releases). Two details that are load-bearing rather than stylistic: - Everything inside the container runs through a LOGIN shell. claude resolves via ~/.profile and codex/gemini/playwright-cli via /etc/profile.d, neither sourced by a bare `devcontainer exec`; without -l they are not found. - down/rm/status/logs go through docker filtered on the devcontainer.local_folder label, since the devcontainer CLI has no teardown or listing commands. Paths are compared normalized because Docker Desktop on Windows varies slash direction and drive-letter case. Testing: the platform-sensitive logic is exposed as pure functions and unit tested with node:test, so Windows behaviour is covered without Windows. A CI matrix over ubuntu/macos/windows additionally exercises dc.cmd under both CMD and PowerShell, asserts the PATHEXT resolution the single-file design depends on, and round-trips install/uninstall. .gitattributes now pins CRLF for .cmd, which the global eol=lf rule would otherwise corrupt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Without Docker, `dc up` previously surfaced the raw CLI failure -- a "spawn docker ENOENT" Node stack trace followed by a JSON blob, none of which tells a user to install Docker. It also took seconds to get there, since npx booted the devcontainer CLI first only to have it fail. Add a preflight to every Docker-dependent command: probe for the binary, then for a live daemon, and print a message naming the fix and the installer URL for the platform. Distinguishes "not installed" from "installed but not running", which need different actions. Fails in ~0.15s. Also add an explicit Node 18+ check. dc.mjs parses on much older Node, so without it a stale runtime fails later and less clearly. Message construction is a pure function (preflightError) so the wording is unit tested; only the probing is impure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On Docker Desktop with the WSL2 backend the devcontainer.local_folder label may not be the plain Windows path, so an unmatched label and a genuinely absent container both surfaced as a bare "no container". They need opposite responses. When nothing matches, list every labelled dev container with its label and print the normalized path being searched for, so a format mismatch is visible rather than silent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things that made `dc claude` look wrong next to the same tool in VS Code's terminal. Colours: `devcontainer exec` forwards no environment by default, so nothing tells the container what the terminal can render and 256-colour/truecolour output degrades to the nearest 16-colour approximation -- Claude Code's orange arrives as red. VS Code sets TERM=xterm-256color and COLORTERM=truecolor itself, which is why it looks right there. Forward the host's values when set, and default to those two when not, which is the Windows case since neither PowerShell nor CMD sets them. These advertise capability rather than forcing colour on, so piped output is unaffected. Docker promo: the docker CLI prints "What's next: try docker debug ..." after commands the devcontainer CLI runs for us, landing in the middle of every `dc claude`. Set DOCKER_CLI_HINTS=false for child processes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gh already came from the github-cli devcontainer feature; glab has no official feature, so fetch the release binary from GitLab directly. The version is resolved from the releases API at build time rather than pinned, and the architecture comes from dpkg, so this works on x86_64 and on ARM (Apple Silicon) containers -- both tarballs are published upstream. Deliberately non-fatal, unlike every other install in this script: glab is a convenience, and gitlab.com is plausibly blocked on the same restricted networks the NPM_REGISTRY option exists for. A failure there should not cost you the agents and browser stack the container is actually for. Verified end to end in a live container (glab 1.114.0). Note the extract must NOT use --strip-components, or bin/glab lands at /usr/local/glab instead of /usr/local/bin/glab and is not on PATH. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Windows job failed because this step drove `dc exec`, and the Docker preflight added later makes that exit non-zero on a runner with no usable Linux-container support. The step was testing the preflight, not what it meant to test. Exercise a standalone shim of the same shape instead, so the check isolates the one layer that cannot be tested from Linux: cmd.exe re-parsing the command line before node sees it. Spaces, ampersands, backslashes and flag-shaped arguments are asserted. Embedded quotes are recorded but not asserted -- that is the known-fragile case through cmd, and if it regresses it is the signal to add a dc.ps1. Note the two assumptions the single-Windows-file design rests on both passed in the failed run: dc.cmd runs under CMD, and bare dc resolves via PATHEXT. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught a real defect in the one-Windows-file design, not a flaky test: 'b' is not recognized as an internal or external command, got: ["a b","a"] want: ["a b","a&b","C:\\Users\\x","--flag"] PowerShell only quotes arguments containing spaces, so a bare `a&b` reaches cmd.exe unquoted, cmd treats & as a command separator, and everything after it is silently discarded -- then the remainder is attempted as a command. So `dc claude "fix A && B"` would lose half its argument on Windows. A batch file cannot recover arguments cmd.exe has already split, so the fix is to keep PowerShell out of cmd entirely. Windows now installs both dc.cmd (for CMD, which cannot execute a .ps1) and dc.ps1. Verified with pwsh that @Args preserves &, quotes, empty strings and flag-shaped arguments, and that the explicit `exit $LASTEXITCODE` is required or callers always see success. CI now asserts behaviour rather than a guess about which extension wins: the .ps1 must be lossless, and whatever a bare name on PATH resolves to must also be lossless, since that is what users actually type. cmd's own lossiness on an unquoted & is recorded but not asserted -- quoting it is normal cmd semantics. Exit-code propagation gets its own check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gh auth login uses the GitHub CLI OAuth app, and GitHub issues one token per app per user: every login regenerates it and invalidates all other copies. With several containers each running gh auth login, they knock each other out -- which is exactly what happened to this session mid-run. A PAT has no such behaviour, so forward GH_TOKEN (and GLAB_TOKEN) from the host instead. gh is then authenticated on every container start with no login, in as many containers at once as you like, and it survives rebuilds -- which a gh auth login does not, since nothing mounts credentials. Unset on the host resolves to empty, which both CLIs ignore, so this is a no-op for anyone not using it (verified against gh 2.46). Tokens use remoteEnv, applied per session, keeping them out of `docker inspect`. NPM_REGISTRY moves to containerEnv and gains the same host passthrough. It is read by setup.sh, a lifecycle command, and whether remoteEnv reaches those is version-dependent -- a silent skip would send every install to the blocked public registry, the exact failure the option exists to avoid. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous assertion was written when dc.cmd was the only Windows shim and went stale the moment dc.ps1 landed. CI answered the open question directly: resolved to ...\\.devcontainer\\dc.ps1 So the two shims self-select correctly -- PowerShell takes the lossless .ps1, CMD takes the .cmd it can actually execute. Assert that preference, since a flip back to dc.cmd would silently cost PowerShell users any argument holding an unquoted ampersand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every assertion in the shim-fidelity step passed -- the ps1 and a bare name on PATH both returned arguments intact, including a&b. The step still failed: the informational cmd.exe probe fails by design (cmd runs the text after the ampersand as a command), and GitHub s pwsh wrapper exits with whatever $LASTEXITCODE holds at the end of the block. Clear it and exit 0 explicitly, so the step reports on its assertions rather than on a probe it is only recording. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds .devcontainer/auth, symlinked onto PATH so `auth` works anywhere in the container. It links to the right token page with the required scopes, reads the pasted token without echoing it, and signs the CLI in. auth infer the target from the repo's git remote auth --gh / --glab / --both / --force A PAT rather than gh's browser flow on purpose: GitHub issues one OAuth token per app per user, so every `gh auth login` invalidates the token in every other container. PATs are independent and any number can be live at once. Reverts the GH_TOKEN/GLAB_TOKEN remoteEnv forwarding added earlier. It only did anything if the host already set those variables, so it solved nothing on its own. NPM_REGISTRY's containerEnv move stays: that one fixes a real bug (setup.sh is a lifecycle command, which remoteEnv may not reach) and needs nothing from the user. Consequence, now stated in both the script and the README: nothing mounts credentials, so a rebuild loses the login and `auth` has to be re-run. Two bugs found while testing: `[ -r /dev/tty ]` is not a usable check for an interactive terminal, since the node is world-readable even with no controlling terminal and only the open fails -- it now opens the fd for real; and the prompt silently reported "skipped" instead of failing when no terminal existed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same trap as the shim-fidelity step, which I fixed in isolation instead of looking for other instances. The assertion passes -- 42 does propagate through the ps1 shim -- but 42 is still in $LASTEXITCODE when the block ends, and GitHub s pwsh wrapper exits with it. Shim fidelity now passes: ps1 and a bare name on PATH both returned ["a b","a&b","C:\\Users\\x","--flag"] intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`gh auth status` exits 0 even when it prints "the token is invalid", so it cannot distinguish being signed in from holding a revoked token. The skip-check used it, so `auth` would report "already authenticated" and do nothing in exactly the case it exists for -- a token another container's `gh auth login` had just invalidated, leaving --force as the only way through. Use a real API call (`gh api user`, `glab api user`) for the liveness check. Verified: dead token now takes the re-authenticate path, live token still skips. Found by answering what --force is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Composes with the existing target selection, so `auth --logout` infers from the remote and `auth --gh --logout` / `--both --logout` narrow it. Safe to offer given how the OAuth token is shared: both CLIs clear local state only. gh documents that logout "does not invalidate authentication tokens", and glab only clears its config file and keyring, so signing out in one container cannot disturb another using the same token. Checked before adding the flag, since a server-side revoke would have made this a footgun. Messaging is per-CLI because their exit codes differ. gh auth status exits 0 whenever a token is stored, valid or not, which is the right question for logout (though wrong for the sign-in check, where it hides a revoked token). glab auth logout exits 0 with nothing stored, so it reports what it did rather than claiming a sign-out that did not happen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Most of it had drifted into explaining why decisions were made -- login shells and ~/.profile, the devcontainer.local_folder label, remoteEnv versus containerEnv and lifecycle commands, which install steps are deliberately non-fatal. That is commit-message material; a reader following the setup does not need it and it made the whole thing read as harder than it is. Reordered to match what someone actually does: what it is, what you get, quick start, try it, sign in, then the optional things (own project, dc, customizing, blocked networks). Advanced material used to sit between the setup steps and the examples. Plainer language throughout: no "agentic", "token-efficient", "shim", "project-agnostic", or "container-create time". Kept every warning that prevents a mistake -- new terminal after installing on Windows, rebuild signs you out, dc shadows the built-in calculator, generated files get overwritten. Fixed two real errors: it credited the single-command browser install to step 7 when it is step 8, and gave a Windows install command predating dc.ps1. Documented dc build, and added dc.ps1 to the file table. 266 lines to 176, with more of it useful. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous pass cut the rationale but still read as machine-written: bolded lead-ins on every bullet, em dashes throughout, "this is for X, not Y", "Two things to know:", everything in tidy threes. Rewritten as prose, with varied sentence length and the structural scaffolding removed. Opens with the actual problem it solves (assistants can write front-end code but cannot see whether it works) instead of a feature list, and the first example produces something visible. Where instructions differ by platform, Windows now comes first, then macOS, then Linux. The install command is the same either way apart from slash direction, since PowerShell resolves the extensionless name itself, so the docs no longer mention dc.cmd or dc.ps1 at all outside the file list. Adds a non-blocking CI probe recording whether CMD also resolves the extensionless name, so the docs can gain a CMD line if it does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running `dc doctor` from a home directory to check the install reported FAIL project no .devcontainer/devcontainer.json found which reads as a broken setup. It isn't one, and the exit code already ignored it. Show a neutral marker and say what to do instead. Also promotes the CMD resolution probe from informational to asserted: it passed, so CMD does resolve the extensionless `.\.devcontainer\dc`, and the README's single Windows command is correct for both shells. Since the docs now rely on that, CI should hold it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous version was built around browser control, which is the part I had spent the most time on rather than the part that matters. The point of the repo is a container that is ready to code in with agents already installed; the browser is one of the things it comes with. Retitled from "AI Browser Control Dev Container". Opens with what you get and why you would want it: agents run somewhere they cannot reach the rest of your machine, you stop setting tools up per project, and all three are there at once. Browser control moves to its own section further down, alongside the other things the container provides. Also drops the bolded lead-ins that had crept back into the new section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dc is a way of running the container, so it belongs with getting started and using it on your own projects rather than after the browser material. Order is now: why, what is inside, getting started, your own projects, dc, signing in, browser, customizing, add-ons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Nothing gets installed on your own machine" reads as though the work happens somewhere else. It does not: it runs on your own hardware, and Docker and the container image are very much on your disk. Say what is actually true instead. It runs locally, and what the container buys you is that the agents and their tools live inside it rather than on your system. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Its three points were already covered elsewhere: isolation in the opening paragraph, per-project setup under using it on your own projects, and the agents and browser under what is inside. Removing it gets the reader to the contents and the actual instructions sooner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removing the social-seo-skill recommendation left one sentence saying Claude ships with the playwright-cli skill, which the browser section already says. Dropped the section and its contents entry rather than leave a heading over duplicated text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops the host environment variable route, leaving the one place most people would actually set it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Getting started read as though VS Code were required, with the terminal route buried several sections below under a heading about skipping VS Code. Someone who does not use VS Code had no reason to keep reading. It now opens by saying there are two ways in that give you the same container, with links for both, and splits into With VS Code and Without VS Code. The terminal path gets enough to be running: install, dc up, dc claude. The later section is now a reference for the rest of the commands, retitled from "Skipping VS Code with dc" to "The dc command" since skipping VS Code is established earlier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"It leaves a couple of working folders behind in your project" implied the copy left them, which made the section read as two different install stories. They are generated, and by three different things: setup.sh writes .playwright, playwright-cli install --skills writes .claude/skills/playwright-cli, and Claude Code writes .claude/settings.local.json as you approve permissions. Say that, and say why none of it should be committed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"None of it is worth committing, so add them to your .gitignore" decides for the reader. Give the reasoning instead -- the first two are regenerated on every build, the third is personal -- and let them choose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Without VS Code section named Node as a requirement and then never used it, and omitted @devcontainers/cli entirely -- the thing that actually builds the container. Following only that section gave you a slow first run with no explanation. Says why Node is needed, and installs the CLI, noting it is strictly optional and what you lose by skipping it. Reading the whole file back turned up the same assumption again in Using it on your own projects, which told you to reopen in VS Code with no mention of dc up. Drops the duplicated optional-CLI note from the dc reference section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The section opened with what dc needs before saying what dc is. Lead with that, and drop the Node version number -- anyone installing Node today gets something far newer than 18, and dc itself reports the requirement in the unlikely case it is not met. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The instructions implied you had to clone this repo to install dc. The installer travels with the .devcontainer folder, so anyone who already copied that into their own project can run it from there. And since install copies dc onto your PATH, the repo is not needed afterwards. Say both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"From here you would normally copy the setup into a project of your own, which is the next section" narrated the document structure instead of saying anything. The next heading already says it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Docker was named as the shared requirement but the folder itself was not -- each path smuggled it in via clone this repo. State both up front: Docker running, and a project containing the .devcontainer folder, whether that is this repo cloned or the folder copied into your own project. That lets both paths start from the project folder, so the VS Code steps no longer assume this repo specifically and the dc path drops its re-explanation of where the folder came from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cloning leaves this repo as the origin remote of what the reader then treats as their project, which is confusing for anyone not comfortable with git -- pushes point somewhere they do not own. The ZIP has no git history and no remote, so there is nothing to untangle, and people who prefer to clone will do so without being told. Also distinguishes trying it out from real work, where you copy the folder into a project that keeps its own git setup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The try-it-out versus real-work split was more machinery than the situation needs. Getting started now just says: download the ZIP, copy .devcontainer into your project. That made Using it on your own projects mostly a repeat of the bullet, so it shrinks to the part that was still new -- what the build generates in your project and whether to track it -- retitled What ends up in your project. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The steps read as though every project needs it. Skip it when already installed; rerunning just updates in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uninstall removed the files but left the registry PATH entry, with a note saying so. Since install only ever registers the exact directory it creates, removing that entry when the last install is gone is safe, and leaves the user PATH as it was found. Kept while any named install remains. CI asserts the round trip on Windows: install registers the directory, uninstalling the last install deregisters it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Not only a Windows situation -- on any OS the current shell may not see a just-installed command. Phrased as symptom then fix, so readers who are not affected read past it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It sat between the install and Then:, so Then appeared to follow the troubleshooting rather than the install. Happy path first, fix afterwards as an aside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The section jumped straight to the auth helper as though it were the required route. Say first that this is optional and most people want one of the two, not both; that the ordinary browser sign-in works from inside the container (gh uses a device code you type into github.com, so no browser is needed in the container); and only then introduce auth as the fix for the one-login-per- account clash, which single-container users will never hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The JSON dump and the no-sandbox and ARM rationale serve someone customizing the browser config, which is rare, and the do-not-edit-generated-files point is already made under What ends up in your project. One sentence keeps the pointer for whoever needs it; the reasoning stays as comments in setup.sh, where the person editing will actually see it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Its headline example, headless mode, is not actually changeable: the container has no display, so headless is the only mode that launches (verified -- headed fails bare, and the xvfb fallback is missing xauth besides). Anyone genuinely customizing the config will find it through setup.sh, whose comments carry the reasoning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught it: "uninstall left ...\Programs\dc on the user PATH". On Windows the payload directory and the bin directory are the same folder, so removing the payload deletes it -- and the cleanup was gated on that folder existing and being empty, a state that cannot occur there. The registry entry was always left behind, exactly what the assertion checks. A missing folder now counts the same as an empty one: the last install is gone either way. The multi-name case is unchanged -- while another named install keeps the folder non-empty, the PATH entry stays. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Windows PATH cleanup still never ran after the previous fix, and CI kept failing on the same assertion. The real cause is a layer lower: capture() uses shell:true on Windows, and Node passes shell arguments unquoted, so cmd.exe interprets the | in the removal script's Where-Object pipeline as its own pipe operator and truncates the script before PowerShell sees it. The install-side script survived only because it happens to contain no pipe. PowerShell is a real executable, so it does not need the shell at all -- that option exists to resolve .cmd shims like npx. Both PS invocations now spawn the exe directly, args passed verbatim. Reproduced the mangling and verified the fix by running the same pipeline both ways against pwsh locally. Failure is no longer silent either: if the cleanup script errors, uninstall prints a warning instead of quietly leaving the PATH entry behind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An independent review found that the PowerShell mangling CI caught was one symptom of a broader defect: run() and capture() used shell:true on Windows, and Node joins shell arguments UNQUOTED. Every multi-word or metacharacter argument was corrupted there -- the login-shell wrapper collapsed so all user arguments to dc claude/codex/gemini/pw/exec were silently dropped, workspace paths with spaces broke, and the tab-separated docker --format string made status/down/rm/logs report "no container" unconditionally. It escaped notice because the shim-level CI tests stop above this layer, and manual testing ran dc claude with no arguments. Replace the layer: spawnSmart() tries a direct no-shell spawn (exact on every OS; also what the PowerShell invocations need), and only for Windows .cmd shims -- which Node refuses to exec directly -- falls back to cmd.exe with a command line quoted by winQuote() under standard MSVCRT rules. Both are exported and unit-tested, including a Windows-only test that round-trips nasty argv through a real .cmd shim on CI. Also from the review: - .devcontainer/auth was committed 644, so on a fresh clone the symlinked `auth` command failed with Permission denied. Now 755 in the index. - auth no longer leaks bash's own /dev/tty error before its message, and no longer says "authenticating" when inferring a target for --logout. - dc --help / dc --version now work as the sole argument. - Dropped an unused import and an orphaned comment. - README: dc uninstall needs the name for named installs; Linux can use plain Docker rather than Docker Desktop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
zeveck
pushed a commit
that referenced
this pull request
Aug 23, 2026
Collapse four devcontainer profiles into a single environment at the repo root (79 tracked files -> 12). The payload is the .devcontainer/ folder, which is self-contained and copies into any project. Container: - Installs Claude Code, Codex CLI, Gemini CLI, gh (devcontainer feature), glab (release binary, arch-aware, non-fatal on restricted networks), @playwright/cli, and Chromium with OS deps. - Browser install consolidated to one command; the old flow downloaded a second, never-used Chromium revision (~650 MB/build) because PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD only affects the npm-postinstall path. - Removed a stale `rm -f .mcp.json` that would have deleted a user's own MCP config when the folder is dropped into another project. - Renamed to "Agentic Dev Environment"; NPM_REGISTRY moved to containerEnv so the lifecycle script reliably sees it. dc (dc / dc.cmd / dc.ps1 / dc.mjs): - Drives the container without VS Code: up, build, shell, exec, claude/codex/gemini/pw, down, rm, status, logs, doctor, install, uninstall. Installs per-machine; project discovered by walking up from the cwd. - In-container commands run through a login shell (required for the agents' PATH) with TERM/COLORTERM forwarded (256-colour output). - Teardown/status via docker, filtered on the devcontainer.local_folder label with normalized path comparison for Docker Desktop on Windows. - Windows ships both shims: PowerShell prefers dc.ps1 (lossless args); CMD uses dc.cmd. Spawns avoid Node's unquoted shell:true join -- direct spawn first, cmd.exe fallback with MSVCRT quoting (winQuote) for .cmd shims only. Windows PATH entry is registered on install and removed on the last uninstall. - Readable preflight errors for missing Docker/Node. auth: - Signs gh/glab in with a pasted PAT (--gh/--glab/--both/--force/--logout, git-remote inference). PATs avoid gh's one-OAuth-token-per-account behaviour, which logs other containers out on every gh auth login. Liveness checked via a real API call because gh auth status exits 0 for an invalid stored token. CI: 3-OS matrix; Windows asserts shim resolution (PATHEXT), argument fidelity through both shims and through spawnSmart's cmd fallback, exit-code propagation, and the install/uninstall PATH round-trip. Unit tests via node --test cover the pure logic (path normalization, exec-arg construction, MSVCRT quoting, preflight messages). README rewritten for a semi-technical audience around the drop-in flow. Reviewed by two independent passes; final verdict yes-with-nits, no blockers. Known fast-follows: cmd-fallback args containing both a quote and a metacharacter, %VAR% expansion in the cmd fallback, and extra argv cases for the Windows fallback test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Landed on main as 59d23ad via a git-level squash (gh was signed out here at merge time, so this PR could not be merged through the API and its diff is now empty). Content is identical to this branch's tip; CI was green on all three platforms at f3ec32c. Closing rather than merging for that mechanical reason only. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Collapses the four devcontainer profiles into a single environment at the repo root, then adds a
dcCLI so the container can be driven without VS Code.Simplification
Promotes
playwright-cli-claude-codex-gemini-python(the only profile in real use) to the root and deletes the rest. 79 tracked files → 5. The payload is now the.devcontainer/folder alone, which is self-contained and copies into any project.Also removed: MCP-era agents and slash commands (they declared
mcp__playwright__*tools a CLI-based container never provides), the vendoredsocial-seoskill (byte-identical to its upstream atzeveck/social-seo-skill, now linked instead), and.claude/settings.local.json(the personal-override slot, not something a template should ship).One removal worth calling out:
setup.shranrm -f .mcp.jsonin the workspace root. Harmless in this repo, but the whole point is dropping the folder into other projects — where it would have silently deleted a user's own MCP config.Browser install fix (~650 MB per build)
Setup installed Chromium twice from sources pinning different revisions — stable
playwright(1234) and@playwright/cli's bundledplaywright-core(1224). Only 1224 is ever launched.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1did not prevent the extra download; that variable is only read by the npm-postinstall path, not the explicitinstallcommand. Verified empirically.Consolidated into one
playwright-cli install-browser chromium --with-deps, which fetches the pinned revision plus the headless shell, ffmpeg, and the OS-level packages.dcdc up,dc shell,dc exec,dc claude|codex|gemini|pw,dc down,dc status,dc logs,dc doctor,dc install.All logic lives in
dc.mjs; the platform entry points (dc,dc.cmd) are one-line shims, so there is nothing to keep in sync. Requiring@devcontainers/clialready guarantees Node on the host, so a single Node implementation costs nothing.One Windows file, not two: CMD cannot execute a
.ps1, so a PowerShell-only install would break CMD users, whiledc.cmdworks in CMD and resolves from PowerShell viaPATHEXT.Two details that are load-bearing rather than stylistic:
clauderesolves via~/.profileandcodex/gemini/playwright-clivia/etc/profile.d, neither sourced by a baredevcontainer exec.down/rm/status/logsgo throughdockerfiltered on thedevcontainer.local_folderlabel, since the devcontainer CLI has no teardown or listing commands. Paths are compared normalized, because Docker Desktop's WSL2 backend varies slash direction and drive-letter case.Also: readable prerequisite errors (missing Docker used to surface a
spawn docker ENOENTstack trace; now it names the fix and fails in ~0.15s), self-diagnosing label mismatches, and terminal-capability forwarding so 256-colour output renders correctly (devcontainer execforwards no env, so Claude Code's orange arrived as red).gh / glab
ghalready came from thegithub-clifeature.glabhas no official feature, sosetup.shfetches the release binary — version resolved at build time, architecture fromdpkg, so x86_64 and ARM both work. It is the one deliberately non-fatal install, since it is a convenience andgitlab.commay be blocked on restricted networks.Testing
14 unit tests via
node:test. The platform-sensitive logic is exposed as pure functions specifically so Windows behaviour is covered without Windows. CI adds a matrix over ubuntu/macos/windows that exercisesdc.cmdunder both CMD and PowerShell, asserts thePATHEXTresolution the single-file design depends on, and round-trips install/uninstall.Manually verified on a Windows host (Docker Desktop, WSL2 backend):
dc installincluding the registry PATH write, a path containing a space, anddc claudein a second unrelated project.Not covered by CI: a real
dc upon Windows — those runners can't usefully run Linux containers.