boxdown 1.0.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +135 -12
  2. package/assets/devcontainer/README.md +65 -21
  3. package/assets/devcontainer/devcontainer.json +27 -15
  4. package/assets/devcontainer/hooks/initialize.sh +76 -22
  5. package/assets/devcontainer/hooks/post-create.sh +70 -12
  6. package/assets/devcontainer/hooks/post-start.sh +20 -13
  7. package/assets/devcontainer/ssh-config-install.sh +12 -3
  8. package/assets/devcontainer/start.sh +721 -44
  9. package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
  10. package/assets/devcontainer/utils/deps-install.sh +68 -0
  11. package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
  12. package/assets/devcontainer/utils/git-signing-bootstrap.sh +109 -0
  13. package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
  14. package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
  15. package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
  16. package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
  17. package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
  18. package/dist/bin/cli.cjs +1 -1
  19. package/dist/bin/cli.mjs +1 -1
  20. package/dist/main-BDgyf2t5.cjs +5758 -0
  21. package/dist/main-J4_2Up3o.mjs +5718 -0
  22. package/dist/main-J4_2Up3o.mjs.map +1 -0
  23. package/dist/main.cjs +5 -2
  24. package/dist/main.d.cts +501 -4
  25. package/dist/main.d.cts.map +1 -1
  26. package/dist/main.d.mts +501 -4
  27. package/dist/main.d.mts.map +1 -1
  28. package/dist/main.mjs +2 -2
  29. package/docs/README.md +1 -0
  30. package/docs/architecture.md +32 -0
  31. package/docs/development.md +13 -6
  32. package/docs/features/README.md +2 -0
  33. package/docs/features/commit-signing.md +94 -0
  34. package/docs/features/generated-config-and-state.md +73 -5
  35. package/docs/features/github-auth-refresh.md +15 -2
  36. package/docs/features/lifecycle.md +103 -11
  37. package/docs/features/setup.md +66 -0
  38. package/docs/features/ssh-config-and-proxy.md +228 -7
  39. package/docs/features/start-and-shell.md +45 -5
  40. package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
  41. package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
  42. package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
  43. package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
  44. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +416 -0
  45. package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
  46. package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
  47. package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
  48. package/docs/testing.md +35 -2
  49. package/docs/todo.md +2 -2
  50. package/package.json +1 -1
  51. package/src/claude-app-config.ts +304 -0
  52. package/src/cli-style.ts +43 -0
  53. package/src/codex-app-config.ts +656 -0
  54. package/src/config.ts +80 -10
  55. package/src/constants.ts +12 -0
  56. package/src/devcontainer.ts +511 -64
  57. package/src/doctor.ts +292 -30
  58. package/src/git-signing.ts +267 -0
  59. package/src/interactive-prompts.ts +692 -0
  60. package/src/list.ts +16 -2
  61. package/src/logging.ts +164 -0
  62. package/src/main.ts +1214 -63
  63. package/src/metadata.ts +52 -3
  64. package/src/package-info.ts +18 -0
  65. package/src/paths.ts +71 -11
  66. package/src/process.ts +80 -2
  67. package/src/progress.ts +593 -0
  68. package/src/purge.ts +216 -0
  69. package/src/shell.ts +25 -0
  70. package/src/ssh-config.ts +134 -16
  71. package/src/ssh-install-targets.ts +111 -0
  72. package/src/ssh-key.ts +53 -13
  73. package/src/status.ts +5 -0
  74. package/dist/main-BuEptwlL.cjs +0 -1707
  75. package/dist/main-ZFTrSVgt.mjs +0 -1685
  76. package/dist/main-ZFTrSVgt.mjs.map +0 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ npm install -g boxdown
28
28
  You can also run it without installing:
29
29
 
30
30
  ```sh
31
- npx boxdown start
31
+ npx boxdown setup
32
32
  ```
33
33
 
34
34
  ## Usage
@@ -36,22 +36,46 @@ npx boxdown start
36
36
  From any project repository on your host:
37
37
 
38
38
  ```sh
39
- npx boxdown start
39
+ npx boxdown setup
40
40
  ```
41
41
 
42
- Boxdown builds or reuses a Dev Container for the current directory, then opens a shell inside it. The target repository stays clean; Boxdown writes generated configuration and SSH keys under user cache/data directories instead of copying `.devcontainer/` into the project.
42
+ Boxdown builds or reuses a Dev Container for the current directory and installs
43
+ an SSH alias for remote tools. The target repository stays clean; Boxdown writes
44
+ generated configuration and SSH keys under user cache/data directories instead
45
+ of copying `.devcontainer/` into the project.
46
+
47
+ Startup commands print concise progress by default and hide raw Docker,
48
+ Dev Containers, and lifecycle hook logs unless a step fails. Add `--verbose` to
49
+ stream the full command output while setup/start work runs. Boxdown also keeps
50
+ one append-only per-workspace command log under its data directory; `boxdown
51
+ status` shows the exact path. Interactive shell, agent, and tunnel session bytes
52
+ are not tee'd into the log.
53
+
54
+ Open an interactive shell inside the container when you need one:
55
+
56
+ ```sh
57
+ npx boxdown start
58
+ ```
43
59
 
44
60
  Boxdown ships and invokes its own `@devcontainers/cli` dependency. It does not require a host/global Dev Containers CLI install.
45
61
 
46
62
  ### Portless SSH
47
63
 
48
- Install an SSH alias for the current project:
64
+ `boxdown setup` installs an SSH alias for the current project. To only install
65
+ or update that alias without starting the devcontainer, use the lower-level SSH
66
+ command:
49
67
 
50
68
  ```sh
51
- npx boxdown ssh-config install
69
+ npx boxdown ssh install
52
70
  ```
53
71
 
54
- By default this creates a `<repo-name>-devcontainer` SSH host. Validate it with:
72
+ This creates a `<repo-name>-devcontainer` SSH host. When run in an interactive
73
+ terminal, Boxdown also asks whether to install optional targets such as Codex
74
+ and Claude.
75
+ Non-interactive runs skip optional targets and print the explicit `--target`
76
+ form to use in scripts.
77
+
78
+ Validate the SSH alias with:
55
79
 
56
80
  ```sh
57
81
  ssh <repo-name>-devcontainer 'whoami && pwd'
@@ -59,32 +83,95 @@ ssh <repo-name>-devcontainer 'whoami && pwd'
59
83
 
60
84
  Use the same alias in Cursor, Claude, Codex, or any SSH-capable tool.
61
85
 
86
+ To also add the project to Codex's remote project sidebar or Claude's SSH
87
+ remote list, pass one or more targets during setup or select them from the
88
+ lower-level SSH prompt:
89
+
90
+ ```sh
91
+ npx boxdown setup --target codex
92
+ npx boxdown setup --target claude
93
+ ```
94
+
95
+ The lower-level SSH command also supports the same targets for scripts:
96
+
97
+ ```sh
98
+ npx boxdown ssh install --target codex
99
+ npx boxdown ssh install --target claude
100
+ ```
101
+
102
+ Restart the target app after installing it so it applies the updated remote
103
+ project config.
104
+
105
+ From the target project directory, forward a dev server running inside the
106
+ container to your host browser:
107
+
108
+ ```sh
109
+ npx boxdown tunnel --port 3030
110
+ ```
111
+
112
+ If `--port` is omitted in an interactive terminal, Boxdown asks which port or
113
+ port mappings to forward and defaults to the generated devcontainer published
114
+ port when available. Non-interactive runs still require `--port`.
115
+
116
+ This keeps a foreground SSH tunnel open until you press Ctrl-C. The host and
117
+ Codex in-app browser can then open `http://localhost:3030/`. Repeat `--port`
118
+ or use `<local:remote>` mappings when needed:
119
+
120
+ ```sh
121
+ npx boxdown tunnel --port 3030 --port 8080:3031
122
+ ```
123
+
124
+ Use `--workspace <path>` only when running the command from a different
125
+ directory. Repeat it with `down` to remove multiple workspace containers in one
126
+ command. When `down` runs from a directory that is not a known Boxdown
127
+ workspace, interactive terminals show a workspace picker instead.
128
+
129
+ Remove Boxdown's managed SSH host block and matching Codex/Claude app entries
130
+ when you no longer need the alias:
131
+
132
+ ```sh
133
+ npx boxdown ssh uninstall
134
+ ```
135
+
62
136
  ### Commands
63
137
 
64
138
  ```sh
139
+ boxdown setup
65
140
  boxdown start
66
141
  boxdown codex
67
142
  boxdown claude
68
- boxdown cc
69
143
  boxdown opencode
70
144
  boxdown antigravity
71
145
  boxdown list
72
146
  boxdown status
73
147
  boxdown stop
74
148
  boxdown down
149
+ boxdown purge
75
150
  boxdown doctor
76
- boxdown ssh-config install
151
+ boxdown ssh install
152
+ boxdown ssh uninstall
77
153
  boxdown ssh-proxy
154
+ boxdown tunnel --port 3030
78
155
  boxdown refresh-gh-token
79
156
  boxdown refresh-gh-token-running
80
157
  ```
81
158
 
82
159
  `boxdown shell` remains supported as an alias for `boxdown start`, but
83
160
  documentation uses `start` as the canonical command.
161
+ `boxdown cc` remains supported as an alias for `boxdown claude`, but
162
+ documentation uses `claude` as the canonical command.
163
+
164
+ `boxdown setup` begins with a host-readiness preflight before it prompts for
165
+ SSH targets, writes workspace state, or starts Docker work. Required failures
166
+ such as an unavailable Docker daemon stop setup with an actionable summary.
167
+ When a local Docker image is available, the preflight also performs a no-pull,
168
+ no-start bind-mount probe for the workspace and Boxdown-managed mount paths.
169
+ Run `boxdown doctor` directly for the complete diagnostic report; an unavailable
170
+ best-effort mount probe is reported as a warning and does not block setup.
84
171
 
85
- The coding-agent commands start or reuse the devcontainer, refresh the selected
86
- CLI, then launch it directly inside the container. Use `--` to pass arguments to
87
- the agent:
172
+ Container bring-up installs Codex and Claude Code by default. The OpenCode and
173
+ Antigravity commands stay available, but install/update those CLIs only when you
174
+ launch them. Use `--` to pass arguments to the selected agent:
88
175
 
89
176
  ```sh
90
177
  boxdown claude -- --continue
@@ -94,18 +181,54 @@ List Boxdown-known devcontainer environments from any directory:
94
181
 
95
182
  ```sh
96
183
  boxdown list
184
+ boxdown list --details
97
185
  boxdown list --json
186
+ boxdown list --format json
98
187
  ```
99
188
 
189
+ Human `boxdown list` output shows `STATE`, `REPO`, `PATH`, and `CONTAINER`.
190
+ Use `boxdown list --details` when you need full copyable paths and SSH aliases
191
+ in human output. Use `boxdown list --json` or `boxdown list --format json` for
192
+ the same structured inventory.
193
+
100
194
  Shared options:
101
195
 
102
196
  ```sh
103
- --workspace <path> # target project directory, defaults to cwd
197
+ --workspace <path> # target project directory, defaults to cwd; repeatable with down; purge also accepts list values
104
198
  --alias <name> # SSH alias, defaults to <repo-name>-devcontainer
199
+ --target <name> # with setup/ssh install, optional target; repeatable; supported: codex, claude
200
+ --port <port> # tunnel port for `boxdown tunnel`; repeatable
105
201
  --recreate # recreate the devcontainer before starting
106
202
  --json # JSON output for status and list
203
+ --format json # JSON output for status and list; equivalent to --json
204
+ --details # detailed human output for list
205
+ ```
206
+
207
+ Use `boxdown purge` when you want to remove the workspace's Boxdown-managed
208
+ environment residue: the devcontainer, its exact recorded Docker image, managed
209
+ SSH/Codex/Claude entries, command log, and Boxdown cache/data for that
210
+ workspace. It does not delete the local repository directory or files inside it.
211
+ Interactive terminals ask for confirmation before purging.
212
+
213
+ For `purge`, `--workspace` accepts the `PATH` or unambiguous `REPO` value
214
+ shown by `boxdown list`. It also accepts exact `SSH ALIAS` values from
215
+ `boxdown status`, `boxdown list --details`, or JSON list output:
216
+
217
+ ```sh
218
+ boxdown purge
219
+ boxdown purge --workspace my-repo-devcontainer
220
+ boxdown purge --workspace my-repo
221
+ boxdown purge --workspace /path/to/my-repo
107
222
  ```
108
223
 
224
+ When `boxdown purge` runs without `--workspace` from a directory that is not a
225
+ tracked Boxdown workspace, interactive terminals show a multi-select list of all
226
+ tracked workspaces, including missing/stale entries. The focused row highlights
227
+ the state token: `running` is green, `exited` is yellow, and `absent`,
228
+ `missing`, or `unknown` are red. Non-interactive runs fail safely from
229
+ untracked directories; scripts should call `boxdown purge --workspace <value>`
230
+ for each workspace.
231
+
109
232
  ## Contributing
110
233
 
111
234
  Please consult [CONTRIBUTING](./CONTRIBUTING.md) for guidelines on contributing to this project.
@@ -1,11 +1,11 @@
1
1
  # Dev container
2
2
 
3
- Run this project in a **consistent Node.js 24 + TypeScript** environment without installing toolchains on your machine. Dependencies install automatically; your repo is the workspace inside the container.
3
+ Run this project in a **consistent Node.js 24 + TypeScript** environment without installing toolchains on your machine. The container starts from the slim Node 24 Debian image, then adds only the shared tools Boxdown needs. Dependencies install automatically; your repo is the workspace inside the container.
4
4
 
5
5
  ## Why use it?
6
6
 
7
- - **Same stack for everyone** — Node 24, pnpm, and tooling match CI and collaborators.
8
- - **Fast onboarding** — Open the folder in a container; `pnpm install` and local git tweaks run once after create.
7
+ - **Same stack for everyone** — Node 24 plus pinned features provide Git, uv, ripgrep, GitHub CLI, and common shell utilities; Debian Python is installed during post-create.
8
+ - **Fast onboarding** — Open the folder in a container; dependency installation and local git tweaks run once after create. pnpm-based workspaces bootstrap `pnpm@11` when needed.
9
9
  - **Host secrets, container dev** — `ANTHROPIC_API_KEY` and `SNYK_TOKEN` are passed from your Mac/Linux session into the container when set locally (see below).
10
10
  - **Optional CLI workflow** — Use `start.sh` if you prefer a terminal-driven container instead of only the editor.
11
11
  - **Portless SSH workflow** — Install a normal SSH host alias that proxies into the devcontainer without publishing an SSH port.
@@ -14,15 +14,27 @@ Run this project in a **consistent Node.js 24 + TypeScript** environment without
14
14
 
15
15
  | File | Role |
16
16
  | ---- | ---- |
17
- | `devcontainer.json` | Image, mounts (e.g. your `~/.gitconfig`), lifecycle commands, env forwarding. |
17
+ | `devcontainer.json` | Slim Node base image, pinned feature set/order, Boxdown state mounts, lifecycle commands, env forwarding. |
18
18
  | `start.sh` | Brings the dev container up with the Dev Containers CLI, then opens a shell **inside** the container or acts as an SSH `ProxyCommand`. |
19
19
  | `ssh-config-install.sh` | Installs/updates a host SSH config alias for Cursor, Claude, or plain `ssh`. |
20
- | `hooks/initialize.sh` | Runs on the host before container create/start; prepares the env file and optional secrets. |
21
- | `hooks/post-create.sh` | Runs once after the container is created — e.g. installs [APM](https://github.com/microsoft/apm) (Agent Package Manager), coding-agent CLIs, and OpenSSH server. |
20
+ | `hooks/initialize.sh` | Runs on the host before container create/start; refreshes private runtime secret files and host Git state. |
21
+ | `hooks/post-create.sh` | Runs once after the container is created — e.g. installs OpenSSH server, Debian Python, [APM](https://github.com/microsoft/apm) (Agent Package Manager), and default coding-agent CLIs. |
22
22
  | `hooks/post-start.sh` | Runs on each container start; refreshes runtime state such as SSH host keys and authorized keys. |
23
+ | `utils/git-config-bootstrap.sh` | Container-side Git config copy/sanitization helper used by lifecycle scripts. |
24
+ | `utils/python-bootstrap.sh` | Container-side Debian Python runtime helper used by lifecycle scripts. |
23
25
  | `utils/ssh-bootstrap.sh` | Container-side OpenSSH install/runtime helper used by lifecycle scripts. |
24
26
  | `utils/coding-agent-cli-update.sh` | Shared install/update helper for Codex, OpenCode, Claude Code, and Antigravity CLI. |
25
- | `utils/deps-install.sh` | Dependency installation helper used by `hooks/post-create.sh`. |
27
+ | `utils/deps-install.sh` | Dependency installation helper used by `hooks/post-create.sh`; bootstraps pnpm/yarn when the slim base does not provide them. |
28
+
29
+ ## Base image
30
+
31
+ Boxdown uses `node:24-trixie-slim` as the base image to keep the shared image smaller than the full Dev Containers TypeScript/Node image. The devcontainer then installs the required operating-system tools through pinned Dev Container features. `common-utils` and `git` run first so later features and lifecycle hooks can rely on shell basics, `sudo`, package metadata, Git, and related utilities.
32
+
33
+ Python is installed during `postCreateCommand` from Debian apt packages (`python3`, `python3-venv`, `python3-pip`, and `pipx`). On Debian trixie this currently provides Python 3.13. Boxdown intentionally avoids the Dev Containers Python feature because it added a large Python runtime/dev-tooling layer, including bundled environments for tools such as mypy, black, pylint, pytest, bandit, pipenv, and flake8.
34
+
35
+ uv remains a separate pinned feature. It provides `uv`/`uvx`, but it does not provide the system Python runtime by default.
36
+
37
+ JavaScript package managers are handled at workspace setup time: npm comes from the Node image, pnpm is installed as `pnpm@11` for pnpm projects, and Yarn is enabled through Corepack when needed.
26
38
 
27
39
  ## Usage
28
40
 
@@ -40,7 +52,7 @@ From the **repository root** on your host:
40
52
  bash .devcontainer/start.sh
41
53
  ```
42
54
 
43
- Requires Docker running. The script resolves Boxdown's packaged `@devcontainers/cli` dependency and uses that binary to `up` the workspace and `exec` into `bash`; it does not install or use a host/global Dev Containers CLI package.
55
+ Requires Docker running. The script resolves Boxdown's packaged `@devcontainers/cli` dependency and uses that binary to `up` the workspace and `exec` into `bash`; it does not install or use a host/global Dev Containers CLI package. Startup output is concise by default; pass `--verbose` to stream raw Docker, Dev Containers, and hook logs.
44
56
 
45
57
  ### GitHub CLI auth from host `gh`
46
58
 
@@ -71,10 +83,13 @@ use HTTPS and ask the container's `gh` for credentials during `git fetch`,
71
83
  `git pull`, and `git push`. This is intentionally tied to the explicit refresh
72
84
  command; regular SSH remote connections do not copy GitHub credentials.
73
85
 
74
- If your host `.gitconfig` is mounted into the container, Boxdown adds local
75
- repository settings that neutralize incompatible host-only helpers such as
76
- `/opt/homebrew/bin/gh` and broad rewrites such as
77
- `url.git@github.com:.insteadOf=https://github.com/`.
86
+ Boxdown snapshots your host `.gitconfig` into workspace state before container
87
+ creation, mounts that snapshot read-only, and copies it to a normal writable
88
+ `/home/node/.gitconfig` during `postCreateCommand`. The container copy is then
89
+ sanitized to neutralize incompatible host-only helpers such as
90
+ `/opt/homebrew/bin/gh`, broad rewrites such as
91
+ `url.git@github.com:.insteadOf=https://github.com/`, and host-only signing
92
+ settings.
78
93
 
79
94
  If the container is already running and you only want to refresh its GitHub auth:
80
95
 
@@ -92,7 +107,7 @@ first when you need to repair host GitHub auth.
92
107
  From the **repository root** on your host, install or update the SSH alias:
93
108
 
94
109
  ```bash
95
- bash .devcontainer/start.sh ssh-config install
110
+ bash .devcontainer/start.sh ssh install
96
111
  ```
97
112
 
98
113
  By default, this creates:
@@ -112,7 +127,7 @@ Then use the same host alias in Cursor, Claude, or any other SSH client:
112
127
  - **SSH Host:** `<repo-name>-devcontainer`
113
128
  - **SSH Port:** leave empty
114
129
  - **Identity File:** leave empty if the client reads `~/.ssh/config`; otherwise use `.devcontainer/.ssh/id_ed25519`
115
- - **Remote Folder:** select `<repo-name>` from the default home folder, or enter `/workspaces/<repo-name>`
130
+ - **Remote Folder:** enter `/workspaces/<repo-name>`
116
131
 
117
132
  To customize the alias, run:
118
133
 
@@ -129,7 +144,7 @@ If you are a coding agent running on the host OS, prefer the SSH alias workflow.
129
144
  From the repository root on the host:
130
145
 
131
146
  ```bash
132
- bash .devcontainer/start.sh ssh-config install
147
+ bash .devcontainer/start.sh ssh install
133
148
  ssh <repo-name>-devcontainer 'whoami && pwd'
134
149
  ```
135
150
 
@@ -138,15 +153,16 @@ Use the generated host alias when configuring the agent:
138
153
  - **SSH Host:** `<repo-name>-devcontainer`
139
154
  - **SSH Port:** leave empty
140
155
  - **Identity File:** leave empty if the agent honors `~/.ssh/config`; otherwise use `.devcontainer/.ssh/id_ed25519`
141
- - **Remote Folder:** select `<repo-name>` from the default home folder, or enter `/workspaces/<repo-name>`
156
+ - **Remote Folder:** enter `/workspaces/<repo-name>`
142
157
 
143
158
  `start.sh` has two modes:
144
159
 
145
160
  - `bash .devcontainer/start.sh` starts or reuses the devcontainer and opens an interactive shell. Use this for a local terminal session.
146
- - `bash .devcontainer/start.sh ssh-config install` installs or refreshes the host SSH alias and exits.
161
+ - `bash .devcontainer/start.sh ssh install` installs or refreshes the host SSH alias and exits.
147
162
  - `bash .devcontainer/start.sh --ssh-proxy` refreshes the SSH alias, starts or reuses the devcontainer, and then bridges SSH over `docker exec`. Do not keep this running manually in a terminal; it is meant to be launched by OpenSSH as the `ProxyCommand` in the generated SSH config.
148
163
  - `bash .devcontainer/start.sh --refresh-gh-token` starts or reuses the devcontainer, then refreshes container `gh` auth from host `gh` when a token is available.
149
164
  - `bash .devcontainer/start.sh --refresh-gh-token-running` refreshes container `gh` auth from host `gh` only when the devcontainer is already running.
165
+ - Add `--verbose` to any startup mode when debugging raw devcontainer, Docker, or hook output.
150
166
 
151
167
  If the devcontainer does not exist yet, the first SSH connection through `<repo-name>-devcontainer` will create it with `@devcontainers/cli up`, including `initializeCommand`, features, mounts, `postCreateCommand`, and `postStartCommand`. The first connection may take longer while the container is created.
152
168
 
@@ -159,17 +175,45 @@ export ANTHROPIC_API_KEY=sk-...
159
175
  export SNYK_TOKEN=...
160
176
  ```
161
177
 
162
- They are wired in `devcontainer.json` under `containerEnv` via `localEnv`.
178
+ Boxdown writes available values to owner-only files in per-workspace runtime
179
+ state outside the repository, then mounts that directory read-only. Bash
180
+ sessions export the values as ordinary environment variables, but Docker
181
+ container configuration and `docker inspect` do not contain their values.
182
+
183
+ When the host `op` CLI can read Boxdown's configured service-account item,
184
+ Boxdown provides `OP_SERVICE_ACCOUNT_TOKEN` through the same runtime mount. A
185
+ missing host value or failed 1Password lookup is non-blocking; the variable is
186
+ simply absent. This token is not a GitHub token and does not authenticate `gh`
187
+ or GitHub Git remotes.
163
188
 
164
- If `OP_SERVICE_ACCOUNT_TOKEN` is present, it authenticates the 1Password CLI.
165
- It is not a GitHub token and does not authenticate `gh` or GitHub Git remotes.
189
+ Boxdown never creates, modifies, reads, or deletes a project
190
+ `.env.development` file. Existing containers created by an older Boxdown need
191
+ `boxdown start --recreate` to stop receiving legacy Docker environment values.
192
+ If an older version left a service-account token in `.env.development` or
193
+ `boxdown.log`, remove it manually and rotate the token.
166
194
 
167
195
  ## Optional customization
168
196
 
169
197
  - **Global agent config** — Boxdown automatically mounts host `~/.agents`
170
198
  read-only at `/home/node/.agents` when that directory exists. Recreate the
171
199
  devcontainer after creating or removing it so the mount set is refreshed.
172
- - **Agent config on the host** — Uncomment the `mounts` entries in `devcontainer.json` to bind `~/.claude`, `~/.gemini`, or `~/.codex` into the container so coding agents see your existing settings.
200
+ - **Codex auth on the host** — Boxdown automatically mounts host
201
+ `~/.codex/auth.json` read-only at `/home/node/.codex/auth.json` when that file
202
+ exists. Recreate the devcontainer after creating or removing it so the mount
203
+ set is refreshed.
204
+ - **Other agent config on the host** — Uncomment the `mounts` entries in
205
+ `devcontainer.json` to bind directories such as `~/.claude` or `~/.gemini`
206
+ into the container so coding agents see your existing settings.
207
+ - **Coding-agent defaults** — Container create/start installs or refreshes
208
+ Codex and Claude Code by default. OpenCode and Antigravity CLI remain
209
+ available through `boxdown opencode` and `boxdown antigravity`, but install
210
+ lazily only when those commands are launched.
211
+ - **Agent CLI cleanup** — After a successful coding-agent CLI install/update,
212
+ Boxdown removes stale agent artifacts: old Codex standalone releases, old
213
+ Claude Code versions, OpenCode installer temp directories, and Antigravity
214
+ staging cache. Codex keeps only the active standalone release by default; set
215
+ `BOXDOWN_CODEX_STANDALONE_RELEASES_KEEP_PREVIOUS` to keep extra rollback
216
+ releases.
173
217
  - **1Password / other CLIs** — Follow the commented blocks in `devcontainer.json` and `hooks/post-create.sh` if you need them; keep the image lean by default.
174
218
 
175
219
  ---
@@ -1,17 +1,35 @@
1
1
  {
2
2
  "name": "Project Devcontainer",
3
- "image": "mcr.microsoft.com/devcontainers/typescript-node:24-trixie",
3
+ "image": "node:24-trixie-slim",
4
4
  "features": {
5
- "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511": {
6
- "version": "3.12",
7
- "installPythonDevDependencies": true
5
+ "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a": {
6
+ "username": "node"
8
7
  },
8
+ "ghcr.io/devcontainers/features/git@sha256:a3e43ff91b9f5f6bd2c14bd510d43e5e698f1266dc41027ba4e04e7e45be607a": {
9
+ "version": "os-provided"
10
+ },
11
+ // Removed from the active feature set after measuring roughly 900MB of
12
+ // layer growth. Keep this reference for future Python 3.12/dev-tooling needs.
13
+ // "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511": {
14
+ // "version": "3.12",
15
+ // "installPythonDevDependencies": true
16
+ // },
9
17
  "ghcr.io/devcontainer-community/devcontainer-features/astral.sh-uv@sha256:36f95b90080248a05aba7b1b3cecf8018bb6d7222f6274f03f050b502abd4fe7": {
10
18
  "shellautocompletion": true
11
19
  },
12
20
  "ghcr.io/devcontainers-extra/features/ripgrep@sha256:0dfc0ac16f0d6aa754d006a138fd4cb4a62c245d308306edf8ad1b7a80b8fdf2": {},
13
21
  "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671": {}
14
22
  },
23
+ "overrideFeatureInstallOrder": [
24
+ "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a",
25
+ "ghcr.io/devcontainers/features/git@sha256:a3e43ff91b9f5f6bd2c14bd510d43e5e698f1266dc41027ba4e04e7e45be607a",
26
+ // Python is installed during post-create from Debian apt to avoid the
27
+ // heavy Dev Containers Python feature layer.
28
+ // "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511",
29
+ "ghcr.io/devcontainer-community/devcontainer-features/astral.sh-uv@sha256:36f95b90080248a05aba7b1b3cecf8018bb6d7222f6274f03f050b502abd4fe7",
30
+ "ghcr.io/devcontainers-extra/features/ripgrep@sha256:0dfc0ac16f0d6aa754d006a138fd4cb4a62c245d308306edf8ad1b7a80b8fdf2",
31
+ "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
32
+ ],
15
33
  // Ask the editor to forward these container ports (no Docker appPort).
16
34
  "forwardPorts": [8787, 3005, 3001, 3000],
17
35
 
@@ -51,24 +69,20 @@
51
69
  },
52
70
 
53
71
  "mounts": [
54
- "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,consistency=cached",
55
72
  // mount host user home directories for agents:
56
73
  // "source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,consistency=cached",
57
- // "source=${localEnv:HOME}/.gemini,target=/home/node/.claude,type=bind,consistency=cached",
58
- // "source=${localEnv:HOME}/.codex,target=/home/node/.codex,type=bind,consistency=cached"
74
+ // "source=${localEnv:HOME}/.gemini,target=/home/node/.claude,type=bind,consistency=cached"
75
+ // Codex auth.json is mounted automatically by Boxdown's generated config when present.
59
76
  ],
60
77
 
61
- // Initialize optional host-side secrets before container create/start.
78
+ // Initialize host runtime state before container create/start.
62
79
  "initializeCommand": "bash .devcontainer/hooks/initialize.sh",
63
80
  // After up, discover the published host binding, e.g.:
64
81
  // docker port <container-name-or-id> 3000/tcp
65
82
  "runArgs": [
66
83
  // publish container port 3000 with dynamic host port (CLI / start.sh)
67
84
  "-p",
68
- "127.0.0.1::3000",
69
- // pass the development environment file to the container
70
- "--env-file",
71
- "${localWorkspaceFolder}/.env.development"
85
+ "127.0.0.1::3000"
72
86
  ],
73
87
  // --
74
88
  "postCreateCommand": "bash .devcontainer/hooks/post-create.sh",
@@ -76,8 +90,6 @@
76
90
  "remoteUser": "node",
77
91
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
78
92
  "containerEnv": {
79
- "NODE_ENV": "development",
80
- "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}",
81
- "SNYK_TOKEN": "${localEnv:SNYK_TOKEN}"
93
+ "NODE_ENV": "development"
82
94
  }
83
95
  }
@@ -1,55 +1,109 @@
1
1
  #!/usr/bin/env bash
2
2
  # initialize: runs on host before container create/start (initializeCommand).
3
- # Prepares .env.development and, when possible, injects OP_SERVICE_ACCOUNT_TOKEN from 1Password.
3
+ # Refreshes private runtime secret files and snapshots host Git config.
4
4
 
5
5
  set -euo pipefail
6
6
 
7
- HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
- WORKSPACE_DIR="${BOXDOWN_WORKSPACE_FOLDER:-$(cd "${HOOKS_DIR}/../.." && pwd)}"
9
- ENV_FILE="${WORKSPACE_DIR}/.env.development"
7
+ HOST_GITCONFIG_PATH="${BOXDOWN_HOST_GITCONFIG_PATH:-${HOME:-}/.gitconfig}"
8
+ HOST_GITCONFIG_SNAPSHOT_PATH="${BOXDOWN_HOST_GITCONFIG_SNAPSHOT_PATH:-}"
9
+ SECRET_ENV_DIR="${BOXDOWN_SECRET_ENV_DIR:-}"
10
10
  OP_TOKEN_REFERENCE="op://Private/1Password op CLI Service Account for DevContainers/password"
11
11
 
12
12
  main() {
13
- ensure_env_file_exists
14
- maybe_inject_1password_service_account_token
13
+ progress "Snapshotting host Git config"
14
+ snapshot_host_gitconfig
15
+ progress "Refreshing runtime secret environment"
16
+ refresh_runtime_secret_environment
15
17
  }
16
18
 
17
- ensure_env_file_exists() {
18
- if [[ ! -f "${ENV_FILE}" ]]; then
19
- : > "${ENV_FILE}"
19
+ progress() {
20
+ if [[ "${BOXDOWN_PROGRESS:-0}" == "1" ]]; then
21
+ printf 'BOXDOWN_PROGRESS: %s\n' "$*"
20
22
  fi
21
23
  }
22
24
 
23
- maybe_inject_1password_service_account_token() {
25
+ snapshot_host_gitconfig() {
26
+ local snapshot_dir
27
+
28
+ if [[ -z "${HOST_GITCONFIG_SNAPSHOT_PATH}" ]]; then
29
+ echo "initialize.sh: host gitconfig snapshot path is not configured; skipping gitconfig snapshot."
30
+ return 0
31
+ fi
32
+
33
+ snapshot_dir="$(dirname "${HOST_GITCONFIG_SNAPSHOT_PATH}")"
34
+ mkdir -p "${snapshot_dir}"
35
+
36
+ if [[ -f "${HOST_GITCONFIG_PATH}" ]]; then
37
+ cp "${HOST_GITCONFIG_PATH}" "${HOST_GITCONFIG_SNAPSHOT_PATH}"
38
+ chmod 0644 "${HOST_GITCONFIG_SNAPSHOT_PATH}"
39
+ return 0
40
+ fi
41
+
42
+ rm -f "${HOST_GITCONFIG_SNAPSHOT_PATH}"
43
+ }
44
+
45
+ prepare_secret_env_dir() {
46
+ if [[ -z "${SECRET_ENV_DIR}" ]]; then
47
+ echo "initialize.sh: runtime secret directory is not configured; skipping optional secrets." >&2
48
+ return 1
49
+ fi
50
+
51
+ umask 077
52
+ mkdir -p "${SECRET_ENV_DIR}"
53
+ chmod 0700 "${SECRET_ENV_DIR}"
54
+ }
55
+
56
+ write_secret_file() {
57
+ local name="$1"
58
+ local value="$2"
59
+ local temporary_path
60
+
61
+ temporary_path="$(mktemp "${SECRET_ENV_DIR}/.${name}.XXXXXX")"
62
+ printf '%s' "${value}" > "${temporary_path}"
63
+ chmod 0600 "${temporary_path}"
64
+ mv -f "${temporary_path}" "${SECRET_ENV_DIR}/${name}"
65
+ }
66
+
67
+ refresh_host_environment_secret() {
68
+ local name="$1"
69
+ local value="${!name:-}"
70
+
71
+ if [[ -n "${value}" ]]; then
72
+ write_secret_file "${name}" "${value}"
73
+ else
74
+ rm -f "${SECRET_ENV_DIR}/${name}"
75
+ fi
76
+ }
77
+
78
+ refresh_1password_service_account_token() {
24
79
  local token
25
80
 
26
81
  if ! command -v op >/dev/null 2>&1; then
27
- echo "initialize.sh: op CLI not found; leaving ${ENV_FILE} unchanged."
82
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
28
83
  return 0
29
84
  fi
30
85
 
31
86
  if ! token="$(op read "${OP_TOKEN_REFERENCE}" 2>/dev/null)"; then
32
- echo "initialize.sh: could not read OP service token from 1Password; leaving ${ENV_FILE} unchanged."
87
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
33
88
  return 0
34
89
  fi
35
90
 
36
91
  if [[ -z "${token}" ]]; then
37
- echo "initialize.sh: OP service token is empty; leaving ${ENV_FILE} unchanged."
92
+ rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
38
93
  return 0
39
94
  fi
40
95
 
41
- upsert_env_var "OP_SERVICE_ACCOUNT_TOKEN" "${token}"
96
+ write_secret_file "OP_SERVICE_ACCOUNT_TOKEN" "${token}"
42
97
  }
43
98
 
44
- upsert_env_var() {
45
- local key="$1"
46
- local value="$2"
47
- local tmp_file
99
+ refresh_runtime_secret_environment() {
100
+ if ! prepare_secret_env_dir; then
101
+ return 0
102
+ fi
48
103
 
49
- tmp_file="$(mktemp)"
50
- awk -F= -v k="${key}" '$1 != k' "${ENV_FILE}" > "${tmp_file}"
51
- printf '%s=%s\n' "${key}" "${value}" >> "${tmp_file}"
52
- mv "${tmp_file}" "${ENV_FILE}"
104
+ refresh_host_environment_secret "ANTHROPIC_API_KEY"
105
+ refresh_host_environment_secret "SNYK_TOKEN"
106
+ refresh_1password_service_account_token
53
107
  }
54
108
 
55
109
  main "$@"