npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meitogi/devcontainer-cli

v0.10.1

Published

Host-side control plane for the meitogi devcontainer sandbox — init wizard, initialize, update, doctor.

Readme

@meitogi/devcontainer-cli

Host-side control plane for the meitogi devcontainer sandbox. Runs on the host, before and around the container — which is what distinguishes it from the tooling baked into the image.

npx @meitogi/devcontainer-cli init

Documentation

The image's documentation is the documentation: what a devcontainer is, what this one adds, the vocabulary, one how-to per common gesture, and troubleshooting by symptom — https://github.com/meitogi/devcontainer-sandbox/blob/master/docs/index.md. Every container built on the image prints that link at startup. This README covers only the host-side commands.

If you have never done this before, start at getting started; it walks devc init question by question.

Status

0.7.0 — four real commands, published on npm.

| Command | State | |---|---| | devc init | implemented — scaffold a project's .devcontainer/ (wizard) | | devc initialize | implemented — host-side pre-container setup | | devc migrate | implemented — read-only report on an install.sh tree and what moving it costs | | devc firewall-mode | implemented — report the firewall mode, or set it | | devc update | stub — bump base + Claude Code versions | | devc doctor | stub — diagnostics |

firewall-mode is the one firewall verb that shipped; the general devc firewall of the design's command table is still absent, as are patch, skill, notify and host — absent rather than stubbed. devc knowledge and devc lessons were dropped for v1 — those files are edited directly.

What this scaffolds

This package alone does nothing for a container — it scaffolds a project that pulls a published image. Two artefacts, two repos, versioned separately:

| Artefact | Source | Distributed via | |---|---|---| | the image | meitogi/devcontainer-sandbox | ghcr.io/meitogi/devcontainer-sandbox:<base>-cc<claude-code> | | the CLI | this repo, meitogi/devcontainer-cli | npm, @meitogi/devcontainer-cli |

Hooks, skills, knowledge and the firewall machinery all live in the image, not in what devc init writes — see EXTENDING.md and the per-stack Dockerfile blocks: stacks/php.md, stacks/android.md, stacks/android-capacitor.md.

devc init

One command, nothing to install first — npx fetches the CLI into its cache and runs it, the way npm create <x> does:

npx @meitogi/devcontainer-cli init [dir]

The wizard looks at what is in the directory (manifests, file extensions) to guess the stack, then asks — saying what each answer is used for:

  1. Stack — Node.js needs nothing; PHP, Android and Capacitor point at the documented Dockerfile blocks in the base repo's stacks/ (see What this scaffolds above).
  2. Project id — DC_PROJECT: the compose project name and the prefix of the project's volumes.
  3. Display name — the devcontainer's name in VS Code.
  4. Claude credentials volume — every claude-creds-* volume on the host is listed with the projects that mount it, most shared first; or a new shared one, or a private per-project one.
  5. Claude Code line — which published <base>-cc<claude-code> image.
  6. Extension patchers — the access token first (a GitHub fine-grained PAT, Contents read-only), then the repo is read from what the token sees: one repo is taken as is, a few are listed (an ext-patch name is the default), more than 20 or no answer from GitHub fall back to typing owner/name. An empty token still takes a typed public repo; empty twice skips the step. The answers can be remembered in ~/.config/devc/ext-patches.env (mode 600), and later projects get a single reuse question.

Then it writes the thin project layer (~45 files: devcontainer.json, Dockerfile, docker-compose.yml, .env, initialize.sh, the firewall allowlist and its worked policy.local.d.example/, the hook and skill overlay directories, claude/CLAUDE-*.md, the host-side helpers, the generic test harness, a blank LESSONS.md, the wtf firewall / wtf ext-patch commands in .devcontainer/.wtfcmd.yaml and at the root), appends a fragment to the root .gitignore, offers to add the missing commands to an existing root .wtfcmd.yaml (matched by group + name, never replacing yours), links LESSONS.md and .claude/rules/* at the root, adds itself as a devDependency to the root package.json and runs npm install — so the npx --yes --package=@meitogi/[email protected] devc initialize that initialize.sh execs resolves the local, lockfile-pinned copy with no registry round-trip. Everything else — hooks, skills, knowledge, the firewall machinery, the toolchain — is inherited from ghcr.io/meitogi/devcontainer-sandbox at runtime.

devc init [dir] [--yes] [--project-id <slug>] [--display-name <name>]
          [--creds-volume <name|none>] [--stack <id>] [--cc <x.y.z>] [--base <x.y.z>]
          [--ext-patches-repo <owner/name>] [--ext-patches-ref <ref>]
          [--no-install] [--dry-run]

--yes takes every default and is required when stdin is not a terminal. Non-interactively, the extension patchers are opted into with EXT_PATCHES_TOKEN in the environment — never a flag, it would land in shell history. Alone, it uses the one repo the token reads; when it reads several, or GitHub does not answer, the step is skipped and the reason printed. --ext-patches-repo names the repo instead:

EXT_PATCHES_TOKEN=github_pat_… npx @meitogi/devcontainer-cli init --yes

The base image pinned is the newest published on the chosen Claude Code line, read from ghcr.io at scaffold time and written as an explicit BASE_IMAGE in .env; offline, the template's own pin is used and the summary says so. --base pins a version by hand.

An existing .devcontainer/ is never overwritten. A tree this CLI scaffolded gets a per-file report and only its missing files added; a tree made by install.sh (v1/v2) or by another tool is refused with a message saying what was found. For an install.sh tree the message points at devc migrate.

devc migrate

npx --yes @meitogi/[email protected] migrate [dir]

The report a tree made by install.sh gets — and nothing else. It reads .devcontainer/, names what it found (the .configured-setup values, the Dockerfile lineage, the initializeCommand and lifecycle lines, the v2 skills loader), sorts every top-level entry by what becomes of it after the switch (config read in place, image now shipped by the base image, pending with no v3 home yet, retired, runtime, yours), and prints the checklist with this project's own values filled in. Exit 0 once the report is printed; 1 when there is nothing to migrate, saying what was found instead.

It writes nothing, and no flag makes it write. Measured on three real v2 trees before it was built: there is no pristine baseline to diff against (install.sh kept no manifest, and the template moved on since each install), the three files the switch must change are the three most hand-edited, and each tree carries subsystems the v3 scaffold has no slot for. A command that rewrote such a tree would have to guess which edits are yours. So the switch is a short checklist of edits git can show and revert, and once it is done — the tree builds on the published image and carries the customizations.stitchu-devc block — devc init recognises it and adds the missing files.

The bash entry point is not a compatibility concession — it is the shipped form. initializeCommand is bash .devcontainer/initialize.sh, and the scaffold writes that script, because the hook runs under /bin/sh -c on Unix but cmd.exe /c on Windows (hardcoded on process.platform in devcontainers/cli), and Git Bash hosts stay win32. A bare command name is the only form both shells resolve. The script finds a Node >= 18 wherever nvm, fnm, asdf, volta or Homebrew keeps it — VS Code resolves the login shell environment on macOS and Linux, but never on Windows and not for the WSL server — then execs the npx line above. A v2 initialize.sh is replaced by this one wholesale, so devcontainer.json's initializeCommand need not change. Add the CLI as a root devDependency so that npx resolves the local copy offline.

devc firewall-mode

npx @meitogi/devcontainer-cli firewall-mode [off|basic|strict]   # host
npx devc firewall-mode [off|basic|strict]                        # container

With no argument it reports the mode and writes nothing. With one it writes firewall/default-mode and then aligns the proxy/CA variables in .env with it — which a bare echo strict > firewall/default-mode does not, leaving .env saying the opposite of the flag. Running it for the mode already set is the repair for exactly that.

It needs no Docker, so the same call works from the host and from inside the container. What it cannot do is apply the change: firewall/ is COPYed into the image at build time, so a rebuild is required and the command says so rather than pretending otherwise. The v2 okeish and paranoid are accepted with a warning and never written back.

Supersedes .devcontainer/firewall-mode.sh, which a v3 tree no longer ships.

devc initialize

The step VS Code runs through initializeCommand, before the container is built. It is a port of the 663-line .devcontainer/initialize.sh, and does, in order:

  1. Classifies the host (mac / linux / wsl / gitbash) and records it in logs/host-os. This is the only code that runs on the host, so it is the only place that can answer — from inside the container the kernel reveals the hypervisor, not the OS.
  2. Opens a timestamped log under logs/, plus a decision trace when DEBUG=1.
  3. Seeds the files the image build depends on: firewall/domains.local.txt, firewall/default-mode, firewall/ports.txt, firewall/policy.local.d/, claude-bridge/config.json, and the .vscode/settings.json stub Docker Desktop on macOS needs before it will bind a file.
  4. Projects team defaults from customizations.stitchu-devc in devcontainer.json into .env, never overwriting a value already there.
  5. Creates the Claude credentials volume.
  6. Decides whether this is a Rebuild, a Reopen or a first run (the container-presence probe) and logs which. Nothing is built locally — the base image is ghcr.io/meitogi/devcontainer-sandbox:<base>-cc<cc>, pulled by compose; bumping that tag is what an upgrade means.
  7. Prompts for the Claude mode on first run, writes the flag files, aligns the proxy variables in .env with the firewall mode, prints a summary, and spawns the host notify daemon.
devc initialize [--devcontainer-dir <path>] [--dry-run]

--dry-run reports every decision and writes nothing — no files, no docker, no daemon.

| Environment | Effect | |---|---| | DEBUG=1 | Structured decision trace beside the log | | DEBUG_REBUILD_CONTEXT=1 | Dump the rebuild-signal diagnostic |

Notable divergences from the bash script

Documented rather than hidden, because the two run side by side for now.

  • .env is parsed, not sourced. Bash handed the file to a shell, so $VAR interpolated and $(…) executed. docker-compose's env_file: reader never did either, so the two consumers of the same file already disagreed. This parser matches compose.
  • .env writes are line edits, never a parse/serialize round-trip — comments, blank lines and ordering are preserved byte for byte.
  • The rolling window repaints on a timer (~20 fps) instead of once per output line, and truncates on code points rather than bytes.
  • DEBUG=1 cannot reproduce set -x. Node has no per-statement hook. The trace records spawns, exits, decisions and file writes instead, in the same + … shape.
  • Team defaults are projected into .env. initialize.sh never read devcontainer.json. This command maps customizations.stitchu-devc.allowLocalAtRebuild to FIREWALL_ALLOW_LOCAL_AT_REBUILD, writing it only when .env does not already set it. No project sets that key yet, so the two implementations currently produce identical .env files — but they will not once one does.
  • The node-not-found diagnostic is gone. Its 180 lines existed because command -v node could fail under VS Code's non-login shell. A Node CLI is already running Node, so that state is unreachable from here.

Requirements

Node 18+, Docker, and VS Code with the Dev Containers extension. On Windows, run from WSL2 or Git Bash — a bare cmd.exe host is refused by name rather than failing later on a missing wslpath.

Development

npm install
npm run build
npm test

Tests are node:test; there are no runtime dependencies and one development dependency.

License

MIT