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.
- package/README.md +135 -12
- package/assets/devcontainer/README.md +65 -21
- package/assets/devcontainer/devcontainer.json +27 -15
- package/assets/devcontainer/hooks/initialize.sh +76 -22
- package/assets/devcontainer/hooks/post-create.sh +70 -12
- package/assets/devcontainer/hooks/post-start.sh +20 -13
- package/assets/devcontainer/ssh-config-install.sh +12 -3
- package/assets/devcontainer/start.sh +721 -44
- package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
- package/assets/devcontainer/utils/deps-install.sh +68 -0
- package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
- package/assets/devcontainer/utils/git-signing-bootstrap.sh +109 -0
- package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
- package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
- package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
- package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
- package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/main-BDgyf2t5.cjs +5758 -0
- package/dist/main-J4_2Up3o.mjs +5718 -0
- package/dist/main-J4_2Up3o.mjs.map +1 -0
- package/dist/main.cjs +5 -2
- package/dist/main.d.cts +501 -4
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +501 -4
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/README.md +1 -0
- package/docs/architecture.md +32 -0
- package/docs/development.md +13 -6
- package/docs/features/README.md +2 -0
- package/docs/features/commit-signing.md +94 -0
- package/docs/features/generated-config-and-state.md +73 -5
- package/docs/features/github-auth-refresh.md +15 -2
- package/docs/features/lifecycle.md +103 -11
- package/docs/features/setup.md +66 -0
- package/docs/features/ssh-config-and-proxy.md +228 -7
- package/docs/features/start-and-shell.md +45 -5
- package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
- package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
- package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
- package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
- package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +416 -0
- package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
- package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
- package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
- package/docs/testing.md +35 -2
- package/docs/todo.md +2 -2
- package/package.json +1 -1
- package/src/claude-app-config.ts +304 -0
- package/src/cli-style.ts +43 -0
- package/src/codex-app-config.ts +656 -0
- package/src/config.ts +80 -10
- package/src/constants.ts +12 -0
- package/src/devcontainer.ts +511 -64
- package/src/doctor.ts +292 -30
- package/src/git-signing.ts +267 -0
- package/src/interactive-prompts.ts +692 -0
- package/src/list.ts +16 -2
- package/src/logging.ts +164 -0
- package/src/main.ts +1214 -63
- package/src/metadata.ts +52 -3
- package/src/package-info.ts +18 -0
- package/src/paths.ts +71 -11
- package/src/process.ts +80 -2
- package/src/progress.ts +593 -0
- package/src/purge.ts +216 -0
- package/src/shell.ts +25 -0
- package/src/ssh-config.ts +134 -16
- package/src/ssh-install-targets.ts +111 -0
- package/src/ssh-key.ts +53 -13
- package/src/status.ts +5 -0
- package/dist/main-BuEptwlL.cjs +0 -1707
- package/dist/main-ZFTrSVgt.mjs +0 -1685
- 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
|
|
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
|
|
39
|
+
npx boxdown setup
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Boxdown builds or reuses a Dev Container for the current directory
|
|
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
|
-
|
|
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
|
|
69
|
+
npx boxdown ssh install
|
|
52
70
|
```
|
|
53
71
|
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
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,
|
|
8
|
-
- **Fast onboarding** — Open the folder in a container;
|
|
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` |
|
|
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;
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
`/
|
|
77
|
-
|
|
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
|
|
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:**
|
|
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
|
|
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:**
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
- **
|
|
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": "
|
|
3
|
+
"image": "node:24-trixie-slim",
|
|
4
4
|
"features": {
|
|
5
|
-
"ghcr.io/devcontainers/features/
|
|
6
|
-
"
|
|
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
|
-
//
|
|
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
|
|
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
|
-
#
|
|
3
|
+
# Refreshes private runtime secret files and snapshots host Git config.
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
if [[
|
|
19
|
-
:
|
|
19
|
+
progress() {
|
|
20
|
+
if [[ "${BOXDOWN_PROGRESS:-0}" == "1" ]]; then
|
|
21
|
+
printf 'BOXDOWN_PROGRESS: %s\n' "$*"
|
|
20
22
|
fi
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
rm -f "${SECRET_ENV_DIR}/OP_SERVICE_ACCOUNT_TOKEN"
|
|
38
93
|
return 0
|
|
39
94
|
fi
|
|
40
95
|
|
|
41
|
-
|
|
96
|
+
write_secret_file "OP_SERVICE_ACCOUNT_TOKEN" "${token}"
|
|
42
97
|
}
|
|
43
98
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
refresh_runtime_secret_environment() {
|
|
100
|
+
if ! prepare_secret_env_dir; then
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
48
103
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 "$@"
|