{"id":50423638,"url":"https://github.com/entropyvortex/buckle","last_synced_at":"2026-05-31T09:31:01.401Z","repository":{"id":361011753,"uuid":"1252713509","full_name":"entropyvortex/buckle","owner":"entropyvortex","description":"One verb for devcontainers — user-wide templates with first-class AI agent support.","archived":false,"fork":false,"pushed_at":"2026-05-28T21:23:05.000Z","size":334,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T21:23:14.646Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/entropyvortex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":[],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":[]}},"created_at":"2026-05-28T19:47:57.000Z","updated_at":"2026-05-28T21:23:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/entropyvortex/buckle","commit_stats":null,"previous_names":["entropyvortex/buckle"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/entropyvortex/buckle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entropyvortex%2Fbuckle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entropyvortex%2Fbuckle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entropyvortex%2Fbuckle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entropyvortex%2Fbuckle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/entropyvortex","download_url":"https://codeload.github.com/entropyvortex/buckle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/entropyvortex%2Fbuckle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33726718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T09:31:00.707Z","updated_at":"2026-05-31T09:31:01.389Z","avatar_url":"https://github.com/entropyvortex.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# buckle\n\n\u003e One verb for devcontainers — generate, build, up, and bash with user-wide templates.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen)](#requirements)\n[![Coverage](https://img.shields.io/badge/coverage-%E2%89%A590%25-success)](#testing)\n[![Status](https://img.shields.io/badge/status-alpha-yellow)](#stability)\n\n`buckle` is a single TypeScript CLI that collapses the daily devcontainer ritual —\nhand-author `.devcontainer/devcontainer.json` → maybe a Dockerfile → maybe a compose file →\n`devcontainer up` → `docker exec -it … bash` — into one verb. It also gives you **user-wide\ntemplates** so the same config travels with *you*, not with each repo.\n\n```bash\n$ buckle up claude-corp1     # build \u0026 up \u0026 bash in\n$ buckle                     # land in any folder; TUI does the right thing\n$ buckle node --feature dod  # render Node template + docker-outside-of-docker\n$ buckle down                # tear it down\n```\n\n---\n\n## Why\n\nDevelopers create, rebuild, and shell into devcontainers many times per day, in many folders.\nDoing this by hand means: copy-paste a `devcontainer.json` from the last project, tweak it,\nrun `devcontainer up`, then a long `docker exec -it … bash` incantation. There is no good\n\"my standard environment\" story across folders.\n\n`buckle` fixes that with three ideas:\n\n1. **Templates are user-wide.** `~/.config/buckle/templates/\u003cname\u003e/template.yaml` — author once,\n   use everywhere. Inheritance with `extends:`. Override with `!replace`.\n2. **One verb does the obvious thing.** Empty folder → wizard. Folder with `.devcontainer/` →\n   status panel. `buckle up \u003ctpl\u003e` builds and bashes. Nothing surprising.\n3. **The same template emits all three artifacts** (`devcontainer.json`, optional `Dockerfile`,\n   optional `docker-compose.yml`) deterministically. Diffs are stable; round-trip is safe.\n\n---\n\n## Install\n\nRequires **Node ≥ 20** and a working **Docker daemon**. The\n[`@devcontainers/cli`](https://github.com/devcontainers/cli) is required for `buckle up` /\n`rebuild` because it correctly wires features and lifecycle hooks; `buckle doctor` will tell\nyou if it's missing.\n\n```bash\nnpm install -g buckle-cli @devcontainers/cli\n```\n\n\u003e **Note**: The package on npm is published as `buckle-cli`, but the executable command is `buckle`.\n\nVerify:\n\n```bash\nbuckle --version\nbuckle doctor\n```\n\n---\n\n## Quickstart\n\n```bash\n$ cd ~/scratch/myproj\n$ buckle                  # opens TUI: detects project, suggests templates, writes .devcontainer\n$ buckle up               # build, up, and drop into a shell\n```\n\nOr non-interactively:\n\n```bash\n$ buckle node --yes --trust    # render the built-in `node` template\n$ buckle up                    # build \u0026 shell in\n```\n\nFor the headline use case:\n\n```bash\n$ buckle up claude-corp        # Node + Claude Code + GitHub CLI + selected MCPs\n```\n\n---\n\n## Commands\n\n| Command | What it does |\n| --- | --- |\n| `buckle` | If no `.devcontainer` exists → wizard. Else → live status panel. |\n| `buckle \u003ctemplate\u003e` | Render `\u003ctemplate\u003e` into `.devcontainer/`. No build. |\n| `buckle up [\u003ctemplate\u003e]` | Generate (if missing), build, up, exec a shell. |\n| `buckle down [--prune]` | Stop \u0026 remove the workspace's container. `--prune` cleans dangling images/volumes. |\n| `buckle bash` | Exec a shell into the running container. |\n| `buckle restart` | Restart in place (no rebuild). |\n| `buckle rebuild` | Down + force-rebuild + up + bash. |\n| `buckle logs [-f] [--tail N]` | Stream container logs. |\n| `buckle status` | Show container state and metadata. |\n| `buckle list [--installed-only]` | List built-in / user / installed templates. |\n| `buckle edit \u003ctemplate\u003e` | Open user template in `$EDITOR`. |\n| `buckle new \u003cname\u003e [--extend \u003cp\u003e]` | Scaffold a new user template. |\n| `buckle install \u003corigin\u003e` | Install a template from `gh:user/repo`, `gl:user/repo`, https git URL, or `file://`. |\n| `buckle uninstall \u003cname\u003e` | Remove a previously installed template. |\n| `buckle doctor` | Diagnose the host environment. |\n\n### Global flags\n\n| Flag | Effect |\n| --- | --- |\n| `--json` | Machine-readable output for every command. |\n| `--verbose` | Extra logs to stderr. |\n| `--yes` | Skip the diff confirmation when writing files. |\n| `--trust` | Skip the lifecycle-trust prompt. |\n| `--feature \u003cspec\u003e` | Add a buckle convenience feature; repeatable. (`--feature dod --feature node:20`) |\n| `--user \u003cuser\u003e` | Run the shell as `\u003cuser\u003e` when bashing in. |\n| `--rebuild` | Force rebuild on `up`. |\n| `--detach` | Don't attach a shell after `up`/`rebuild`. |\n| `--git-init` | Initialize a git repo in the workspace if there is none. |\n| `--preview`, `--dry-run` | Show what *would* be written without touching disk. |\n\n---\n\n## Templates\n\nA buckle template is a YAML file at\n`~/.config/buckle/templates/\u003cname\u003e/template.yaml`. Built-ins ship with the binary; user templates\noverride built-ins with the same name; installed templates (via `buckle install`) live in their\nown subtree.\n\n### Minimal example\n\n```yaml\n# ~/.config/buckle/templates/claude-corp1/template.yaml\nname: Claude Corp 1\ndescription: Node + Claude Code with my team's MCP set.\nversion: 0.1.0\n\nextends: claude-corp           # inherit from a built-in\n\nfeatures:\n  - claude-code\n  - grok\n  - mcp:filesystem\n  - mcp:github\n\nenv:\n  ANTHROPIC_LOG: warn\n\nlifecycle:\n  postCreate:\n    - npm install -g pnpm\n  postAttach:\n    - \"echo 'corp1 ready'\"\n```\n\n### Full schema (cheat sheet)\n\n```yaml\nname: Display name\ndescription: Long-form description.\nversion: 0.1.0\n\n# Inheritance: single parent or ordered MRO array (rightmost wins on conflicts).\nextends: ubuntu-base\n# extends: [base, mixin-a, mixin-b]\n\n# EXACTLY ONE of image / build / compose may be set per template level.\nimage: mcr.microsoft.com/devcontainers/base:ubuntu\nbuild:\n  dockerfile: Dockerfile\n  context: .\n  args: { NODE_VERSION: \"20\" }\n  target: dev\ncompose:\n  file: docker-compose.yml\n  service: app\n  runServices: [app, db]\n  shutdownAction: stopCompose\n\n# Buckle convenience features (sugar). Compile to native devcontainer features + hooks.\nfeatures:\n  - dod                  # docker-outside-of-docker\n  - dind                 # docker-in-docker (privileged)\n  - gh\n  - git-config\n  - claude-code\n  - grok                  # Grok Build (xAI agentic CLI/TUI)\n  - mcp:filesystem\n  - aws\n  - gcloud\n  - kube\n  - terraform\n  - node:20\n  - python:3.12\n  - go:1.22\n  - rust:1.75\n  - java:21\n  # native devcontainer features pass through:\n  - ghcr.io/devcontainers/features/git:1\n\n# Pass-through native devcontainer features (advanced).\nnativeFeatures:\n  ghcr.io/devcontainers/features/python:1: { version: \"3.12\" }\n\nforwardPorts:\n  - 3000\n  - { port: 8080, label: api, onAutoForward: notify }\nappPort: 3000\n\nmounts:\n  - { source: \"${localEnv:HOME}/.aws\", target: /home/vscode/.aws, type: bind, readOnly: true }\n\nenv:\n  NODE_ENV: development\n\nrunArgs:\n  - \"--cap-add=SYS_PTRACE\"\n  - \"--init\"\n\ncustomizations:\n  vscode:\n    extensions: [dbaeumer.vscode-eslint, esbenp.prettier-vscode]\n    settings: { \"editor.formatOnSave\": true }\n\nremoteUser: vscode\ncontainerUser: vscode\nworkspaceFolder: /workspaces\n\n# Lifecycle hooks. Append-merge by default; first element \"!replace\" replaces parent.\nlifecycle:\n  initialize:    [ ./bin/preflight ]\n  onCreate:      [ ]\n  updateContent: [ ]\n  postCreate:    [ corepack enable, npm ci ]\n  postStart:     [ ]\n  postAttach:    [ \"node --version\" ]\n```\n\n### Inheritance \u0026 merging\n\n- `extends:` accepts a string or an ordered list (MRO; rightmost wins).\n- Cycles are detected; depth is capped at 8.\n- Plain objects merge key-by-key.\n- Arrays append by default. To replace the parent's array, prefix with the literal `!replace`:\n\n  ```yaml\n  lifecycle:\n    postCreate:\n      - \"!replace\"\n      - echo \"this fully overrides the parent's postCreate\"\n  ```\n\n- `image` / `build` / `compose` are mutually exclusive *at any single level*. A child's choice\n  replaces the parent's.\n\n### Common patterns \u0026 gotchas\n\n#### Lifecycle hook ordering\n\nHooks run in this order (the devcontainer spec defines the timing):\n\n| Hook            | When it runs                                      | Typical use |\n|-----------------|---------------------------------------------------|-------------|\n| `initialize`    | Very early, on the host (before container exists) | Preflight scripts, secret fetching |\n| `onCreate`      | Once, when the container is first created        | One-time heavy setup (tool installs that don't change often) |\n| `updateContent` | When content is refreshed (git pull, etc.)       | `npm ci`, `go mod download` |\n| `postCreate`    | After the container is created and code is present | Most common place for `npm install -g`, database migrations, `corepack enable` |\n| `postStart`     | Every time the container starts                  | Lightweight background services |\n| `postAttach`    | Every time you `buckle bash` or attach in VS Code | Welcome messages, `node --version`, `git status` |\n\n**Tip**: Use `!replace` at the start of a list when you want to completely override a parent's hooks instead of appending.\n\n#### Variable expansion in mounts and env\n\nBuckle (and the underlying devcontainer tooling) supports `${localEnv:VAR}` syntax. This is **not** shell expansion — it is performed by the devcontainer CLI / VS Code when the container is created.\n\n```yaml\nmounts:\n  - source: \"${localEnv:HOME}/.aws\"\n    target: /home/vscode/.aws\n    type: bind\n    readOnly: true\n\n  - source: \"${localEnv:HOME}/.claude\"\n    target: /home/vscode/.claude\n    type: bind\n```\n\n- Use `${localEnv:HOME}` rather than hard-coding `/Users/you` or `/home/you`.\n- The variable must exist in your shell environment when you run `buckle up` / the wizard.\n- For secrets or tokens, prefer mounting a directory or using a short-lived `initialize` script rather than embedding them in the template.\n\n#### Using both Claude Code and Grok Build\n\nBuckle treats having **both** major agentic coding agents in the same devcontainer as a first-class experience.\n\n```bash\n# Quick start with both agents\nbuckle up --feature claude-code --feature grok\n\n# Or use the dedicated template\nbuckle new myproject --extend ai-native\n```\n\n**Why run both?**\n\n- They have different strengths and personalities.\n- You can route different tasks to the agent that performs best on that workload.\n- Their context windows, tool use, and reasoning styles are complementary.\n\n**How buckle supports the combination:**\n\n- Separate, persistent mounts: `~/.claude` and `~/.grok`\n- Both agents' official installers run cleanly in `postCreate`\n- No conflicts in configuration or PATH handling\n- The `ai-native` built-in template is specifically designed around this dual-agent setup\n\nYou can also mix and match freely in your own templates:\n\n```yaml\nfeatures:\n  - claude-code\n  - grok\n  - mcp:filesystem\n  - mcp:github\n```\n\nSee the `ai-native` and `claude-corp` built-in templates for realistic examples of this pattern in action.\n\n**Important: The safety model and philosophy of the `ai-native` template**\n\nThis template is **deliberately** built to give Claude Code and Grok Build **maximum, unrestricted power** inside the container:\n\n```bash\nalias claude='claude --dangerously-skip-permissions'\nalias grok='grok --yolo'\n```\n\n**This is not a bug or oversight.** It is the *entire point* of this template.\n\n### Why this exists\n\n- The container is a **throwaway, high-trust sandbox**.\n- Your real source code lives on the host and is only bind-mounted in.\n- If something goes wrong, you can `rm -rf .devcontainer \u0026\u0026 buckle up ai-native --rebuild` and start fresh in seconds.\n- The agents run as the `vscode` user (with full write access to the mounted workspace).\n- The dangerous flags (`--dangerously-skip-permissions` / `--yolo`) are enabled by default because the whole purpose of this environment is to let the agents move fast without asking for confirmation on every file edit, terminal command, or package install.\n\nIf you do **not** want agents to have this level of autonomy, do not use the `ai-native` template (or extend it and remove the aliases).\n\nThis design is intentional and documented. The container is not meant to be a \"safe\" daily driver for your host machine — it is a disposable, high-agency environment for AI coding agents.\n\n#### Working with compose (multi-service)\n\nWhen you set `compose:` instead of `image` or `build`, buckle generates a minimal `docker-compose.yml` on first use.\n\nImportant realities:\n- Lifecycle hooks (`postCreate`, etc.) run **only against the primary `service`** you declared.\n- If you need to run commands in other services, use `docker compose exec \u003cservice\u003e ...` inside your hooks.\n- Features like `dind` and `dod` only affect the primary service unless you duplicate the configuration in your compose file.\n- The generated compose file is a **starting point**. Edit it freely after the first render.\n\nSee the `compose-demo` built-in template (run `buckle new myapp --extend compose-demo`) and [docs/PATTERNS.md](docs/PATTERNS.md) for deeper multi-service guidance, including how hooks interact with secondary services and common customizations people make after first render.\n\n#### Realistic corporate inheritance example\n\n```yaml\n# ~/.config/buckle/templates/my-corp-base/template.yaml\nname: My Corp Base\nextends: ubuntu-base\nfeatures:\n  - gh\n  - git-config\n  - aws\nlifecycle:\n  postCreate:\n    - corepack enable\n    - npm install -g pnpm@latest\n\n# ~/.config/buckle/templates/claude-corp1/template.yaml\nname: Claude Corp 1\nextends: my-corp-base          # your internal base\nfeatures:\n  - claude-code\n  - grok                       # Both Claude Code and Grok Build as first-class citizens\n  - mcp:filesystem\n  - mcp:github\nenv:\n  ANTHROPIC_LOG: warn\nmounts:\n  - source: \"${localEnv:HOME}/.claude\"\n    target: /home/vscode/.claude\n  - source: \"${localEnv:HOME}/.grok\"\n    target: /home/vscode/.grok\n```\n\nHaving **both** Claude Code and Grok Build in the same devcontainer is explicitly a first-class, well-supported experience in buckle. The two tools compose cleanly (separate config directories, separate postCreate install steps).\n\nThis pattern lets you keep company policy in one place while individual teams or roles add their own layers.\n\n### Built-in templates\n\n| Name | Description |\n| --- | --- |\n| `ubuntu-base` | Plain Ubuntu LTS with the `vscode` user. Good base to extend. |\n| `node` | Node.js 20 (Bullseye), corepack pre-enabled. |\n| `python` | Python 3.12 with pip / venv / uv. |\n| `go` | Go 1.22 with delve, gopls, persistent module cache. |\n| `rust` | Rust stable, rust-analyzer, persistent target cache. |\n| `bun` | Bun runtime on Debian. |\n| `deno` | Deno runtime, secure by default. |\n| `polyglot` | Node + Python + Go on a Debian universal base. |\n| `claude-corp` | Node + Claude Code + GitHub CLI + selected MCPs (good real-world reference). |\n| `compose-demo` | Minimal compose-based multi-service starter (app + sidecar pattern). |\n| `ai-native` | **First-class dual-agent setup**: Claude Code + Grok Build together, plus common MCPs and tooling. The recommended starting point when you want both major AI coding agents available. |\n\nRun `buckle list` to see the live catalog (built-in + user + installed).\n\n### Installing third-party templates\n\n```bash\nbuckle install gh:acme/devcontainer-templates/node-strict\nbuckle install gh:acme/devcontainer-templates#v2\nbuckle install gl:acme/templates\nbuckle install https://example.com/x.git#v1\nbuckle install file:///abs/path/to/template-dir\n```\n\nInstalled templates land under\n`~/.config/buckle/templates/_installed/\u003corigin-hash\u003e/\u003ctemplate-name\u003e/`. They appear in\n`buckle list` with an `installed (origin-hash)` marker.\n\n---\n\n## The 10× developer flow\n\n```bash\n# Land in a brand-new project, no devcontainer.\n$ cd ~/code/some-fresh-clone\n$ buckle             # TUI: detects 'package.json' + 'pnpm-lock.yaml' → suggests `node`\n                     # Pick template, toggle features, hit \"u\" → writes .devcontainer \u0026 ups.\n\n# Iterate.\n$ buckle             # already has .devcontainer → status panel: r rebuild · u up · s/d down · b bash\n\n# Try a quick docker-in-docker test.\n$ buckle up --rebuild --feature dind\n\n# Save your favorite stack as a personal template.\n$ buckle new claude-corp1 --extend claude-corp\n# … edit template.yaml in $EDITOR …\n$ buckle up claude-corp1\n```\n\n---\n\n## TUI\n\nTwo flows, picked by whether the cwd already has `.devcontainer/devcontainer.json`:\n\n**Wizard** (no devcontainer) — auto-detects language signals, suggests top templates, lets you\ntoggle features, previews changes, then either *just writes* (`y`) or *writes-and-ups* (`u`).\n\n**Status panel** (devcontainer present) — a one-screen dashboard:\n\n```\n╭───╮  buckle\n╰───╯  one verb for devcontainers\n\nworkspace: /home/me/myproj\ncontainer: buckle.myproj.node\nstatus:    running\nimage:     mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye\nports:     3000 → 3000/tcp\n\nr rebuild · u up · s/d down · b bash · q quit\n```\n\nRefresh interval defaults to 5 s; override with `BUCKLE_STATUS_REFRESH=2000`.\n\n---\n\n## JSON mode\n\nEvery command supports `--json`. The envelope:\n\n```jsonc\n{\n  \"ok\": true,\n  \"timestamp\": \"2026-05-08T12:34:56.789Z\",\n  \"workspace\": \"/home/me/proj\",\n  \"data\": { \"...\": \"...\" }\n}\n```\n\nOn error:\n\n```jsonc\n{\n  \"ok\": false,\n  \"timestamp\": \"...\",\n  \"error\": { \"code\": \"E_DOCKER_DOWN\", \"message\": \"...\", \"hint\": \"...\" }\n}\n```\n\nError codes are stable: `E_DOCKER_DOWN`, `E_TEMPLATE_NOT_FOUND`, `E_TEMPLATE_INVALID`,\n`E_TEMPLATE_CONFLICT`, `E_HOOK_FAILED`, `E_BUILD_FAILED`, `E_PORT_CONFLICT`, `E_HASH_MISMATCH`,\n`E_CYCLE`, `E_PERMISSION`, `E_NO_GIT`, `E_USER_ABORT`, `E_INSTALL_FAILED`, `E_UNSUPPORTED`,\n`E_INTERNAL`.\n\n---\n\n## Trust model\n\nTemplates can run shell commands (`postCreate`, `postStart`, …) and mount paths from your host\nfile system. `buckle` therefore prompts you on first use of any template whose\n**executable surface** (lifecycle, mounts, runArgs, features, native features, customizations)\nis unseen. The trust store lives at `~/.config/buckle/trust.json` and maps the merged-template\nSHA-256 to the date you trusted it. If the surface changes, you're prompted again.\n\nYou can:\n\n- pass `--trust` to skip the prompt for that one run\n- inspect a template before trusting with `buckle edit \u003cname\u003e`\n- review what would be written without committing with `--preview` / `--dry-run`\n\n`buckle` does no network access during template resolution. `buckle install` does network\nclones via `git`. `buckle doctor` is the only command that probes outside the workspace.\n\nSee [SECURITY.md](SECURITY.md) for the full threat model.\n\n---\n\n## Compatibility\n\n- **Linux** (amd64, arm64) — first-class.\n- **macOS** (Apple Silicon) — first-class with Docker Desktop, OrbStack, or Colima. Buckle\n  inherits `DOCKER_HOST` / `DOCKER_CONTEXT` from your shell, so any of these work.\n- **WSL2** — works; run buckle from inside WSL.\n- **Podman** — supported when a Docker-compatible API socket is available (via `DOCKER_HOST`\n  or `alias docker=podman`). Many devcontainer features and the official `@devcontainers/cli`\n  have reduced functionality. Run `buckle doctor` to see the detected runtime (`container.runtime`).\n  First-class support is a v1.x goal.\n\n`buckle` requires the [`@devcontainers/cli`](https://github.com/devcontainers/cli) for\n`up` / `rebuild`; `buckle doctor` will tell you what's missing.\n\nLifecycle hooks (`postCreateCommand` etc.) are always emitted as a single flat string\njoined with `\u0026\u0026`. If a template declares per-step `user:` on a hook, buckle folds it\naway — the whole hook runs as the template's `remoteUser` (which is `vscode` in every\nbuilt-in). The `{ command, user }` per-step form crashes `@devcontainers/cli` (still\nbroken in 0.87.0) and isn't part of the devcontainer JSON spec for named-object hooks.\n\n---\n\n## Configuration\n\nPer-user file at `~/.config/buckle/config.yaml` (optional):\n\n```yaml\neditor: code              # falls back to $VISUAL → $EDITOR → vi\ndefaultTemplate: node     # used by the wizard if autodetect can't decide\n```\n\nEnvironment variables:\n\n| Variable | Effect |\n| --- | --- |\n| `BUCKLE_SHELL` | Preferred shell when `buckle bash`. Default: zsh \u003e bash \u003e sh. |\n| `BUCKLE_STATUS_REFRESH` | Status-panel refresh in ms (default 5000). |\n| `BUCKLE_BUILTIN_DIR` | Override built-in template directory (testing). |\n| `BUCKLE_NO_COLOR` | Strip ANSI from output (also honors `NO_COLOR`). |\n| `BUCKLE_DEBUG` | Print full stack traces on uncaught errors. |\n\n---\n\n## Testing\n\nRun the full suite (179 tests at the time of writing):\n\n```bash\nnpm test\nnpm run test:coverage   # coverage thresholds: 90% lines/statements/functions, 80% branches\n```\n\nCoverage excludes the docker-subprocess plumbing layer and the TUI render layer — those are\nexercised by integration runs against a real docker daemon (`make integration` in CI).\n\n---\n\n## Architecture\n\n```\nsrc/\n  cli/                # argument parsing, render pipeline, JSON envelope, command shells\n    commands/         # one file per subcommand\n    parse.ts          # commander program \u0026 `buckle \u003ctemplate\u003e` rewrite\n    render.ts         # resolve → trust → plan → apply\n    install.ts        # gh: / gl: / https / file:// origins\n  templates/\n    schema.ts         # zod schema (single source of truth)\n    loader.ts         # discovery (built-in / user / installed)\n    resolver.ts       # extends, deep merge, !replace, cycle detection, hashing\n    autodetect.ts     # project signals → suggested templates\n    trust.ts          # SHA-256 trust store\n    builtin/          # bundled templates\n  features/\n    catalog.ts        # buckle convenience-feature catalog\n    compile.ts        # convenience features → native features + hooks/env/mounts\n  generators/\n    devcontainer.ts   # Template → devcontainer.json (deterministic, sorted)\n    dockerfile.ts     # minimal Dockerfile when build:.dockerfile is missing\n    compose.ts        # single-service docker-compose.yml when compose: is set \u0026 missing\n    writer.ts         # plan / apply / preview / diff\n  docker/\n    naming.ts         # buckle.\u003ccwd\u003e.\u003ctemplate\u003e with collision suffix\n    inspect.ts        # 5-state status (absent | built | running | dead | broken)\n    devcontainer-cli.ts  # @devcontainers/cli wrapper\n    driver.ts         # high-level: status, up, down, bash, restart, logs\n  tui/\n    Wizard.tsx        # interactive setup\n    StatusPanel.tsx   # interactive dashboard\n  util/               # logging, paths (XDG), errors, slug, fs\n```\n\n---\n\n## Stability\n\n`buckle` is **alpha**. The CLI surface, JSON shape, error codes, and template schema are\nstable for 0.x but may shift before 1.0. Expect to pin a specific version in CI.\n\nWe follow semver:\n\n- Pre-1.0: minor versions can be breaking; PATCH versions are bug-fix only.\n- Post-1.0: breaking changes only on MAJOR.\n\n---\n\n## Contributing\n\nPRs welcome. The [4-round design consensus](docs/DESIGN-NOTES.md) is documented; if you'd\nchange the spec, please reference it.\n\nTo work on buckle locally:\n\n```bash\ngit clone https://github.com/buckle-dev/buckle.git\ncd buckle\nnpm install\nnpm run build\nnpm test\nnode bin/buckle.mjs --help\n```\n\nStyle: TypeScript strict, ESM, Node ≥ 20, vitest, ink, commander, zod. Lint with `npm run lint`,\nauto-format with `npm run format`. Coverage thresholds in `vitest.config.ts` are enforced in CI.\n\n---\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentropyvortex%2Fbuckle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fentropyvortex%2Fbuckle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fentropyvortex%2Fbuckle/lists"}