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
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
## Commands
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
boxdown
|
|
6
|
+
boxdown setup --target codex
|
|
7
|
+
boxdown setup --target claude
|
|
8
|
+
boxdown ssh install
|
|
9
|
+
boxdown ssh install --target codex
|
|
10
|
+
boxdown ssh install --target claude
|
|
11
|
+
boxdown ssh uninstall
|
|
7
12
|
boxdown ssh-proxy
|
|
13
|
+
boxdown tunnel --port 3030
|
|
8
14
|
```
|
|
9
15
|
|
|
10
|
-
`boxdown ssh
|
|
11
|
-
`boxdown ssh
|
|
16
|
+
`boxdown ssh` is accepted as a convenience shortcut for
|
|
17
|
+
`boxdown ssh install`, but docs use the explicit install form.
|
|
18
|
+
`boxdown setup` is the higher-level workflow when you also want to start or
|
|
19
|
+
recreate the devcontainer before installing the SSH alias.
|
|
12
20
|
|
|
13
21
|
`ssh-proxy` is primarily an internal command launched by OpenSSH as a
|
|
14
22
|
`ProxyCommand`.
|
|
@@ -31,11 +39,103 @@ The generated SSH config block includes:
|
|
|
31
39
|
The block is wrapped in Boxdown markers so repeated installs replace the managed
|
|
32
40
|
block instead of duplicating it.
|
|
33
41
|
|
|
42
|
+
When `boxdown ssh install` runs without `--target` in an interactive terminal,
|
|
43
|
+
Boxdown asks which optional install targets to update in addition to the SSH
|
|
44
|
+
alias. The menu supports selecting multiple targets or skipping all optional
|
|
45
|
+
targets. In non-interactive contexts, Boxdown skips optional targets instead of
|
|
46
|
+
blocking and prints the explicit `--target` form for scripts.
|
|
47
|
+
|
|
48
|
+
`boxdown ssh uninstall` removes the Boxdown-managed marker block for the
|
|
49
|
+
selected alias, the matching Codex app remote project entry, and the matching
|
|
50
|
+
Codex persisted sidebar state for the workspace. It leaves unrelated OpenSSH
|
|
51
|
+
config entries, unrelated Codex remote projects, generated state, and SSH key
|
|
52
|
+
files in place.
|
|
53
|
+
|
|
34
54
|
`boxdown status` reports whether that Boxdown-managed block is `installed`,
|
|
35
55
|
`missing`, or `outdated`. It only recognizes blocks wrapped in Boxdown's marker
|
|
36
56
|
comments; an unrelated OpenSSH `Host` entry with the same alias is not treated
|
|
37
57
|
as an installed Boxdown alias.
|
|
38
58
|
|
|
59
|
+
## Codex App Target
|
|
60
|
+
|
|
61
|
+
Selecting Codex from the interactive prompt or running
|
|
62
|
+
`boxdown ssh install --target codex` keeps the normal SSH install flow and also
|
|
63
|
+
writes a Codex app remote project entry for the same alias. `--target` is
|
|
64
|
+
repeatable so future optional SSH install targets can be combined in one
|
|
65
|
+
install command.
|
|
66
|
+
|
|
67
|
+
The Codex app config is written to:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
~/.codex/codex-app/config.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`BOXDOWN_CODEX_APP_CONFIG` overrides this path for tests and local development.
|
|
74
|
+
|
|
75
|
+
The generated Codex entry points at the canonical container workspace path:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"sshAlias": "<repo-name>-devcontainer",
|
|
80
|
+
"projects": [
|
|
81
|
+
{
|
|
82
|
+
"remotePath": "/workspaces/<repo-name>",
|
|
83
|
+
"label": "<repo-name>"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Boxdown merges by SSH alias and normalized remote path, so repeated installs
|
|
90
|
+
update the existing Codex project instead of duplicating it. If an older
|
|
91
|
+
Boxdown install registered `/home/node/<repo-name>`, the next Codex target
|
|
92
|
+
install migrates that entry to `/workspaces/<repo-name>`. Existing known Codex
|
|
93
|
+
config keys are preserved, but unknown keys are not written back because
|
|
94
|
+
Codex's app config parser is strict.
|
|
95
|
+
|
|
96
|
+
Boxdown normalizes matching Codex sidebar state for the same SSH alias when it
|
|
97
|
+
installs the Codex target. Restart Codex after installing the target so Codex
|
|
98
|
+
applies the app config, discovers the SSH alias from normal OpenSSH config, and
|
|
99
|
+
creates or updates its sidebar project entry.
|
|
100
|
+
|
|
101
|
+
## Claude App Target
|
|
102
|
+
|
|
103
|
+
Selecting Claude from the interactive prompt or running
|
|
104
|
+
`boxdown ssh install --target claude` keeps the normal SSH install flow and also
|
|
105
|
+
writes a Claude app SSH remote entry for the same alias. `--target` is
|
|
106
|
+
repeatable, so `codex` and `claude` can be installed in one command.
|
|
107
|
+
|
|
108
|
+
The Claude SSH config is written to:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
~/Library/Application Support/Claude/ssh_configs.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`BOXDOWN_CLAUDE_SSH_CONFIGS` overrides this path for tests and local
|
|
115
|
+
development.
|
|
116
|
+
|
|
117
|
+
The generated Claude entry mirrors the desktop app's SSH remote shape:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"configs": [
|
|
122
|
+
{
|
|
123
|
+
"name": "<repo-name>",
|
|
124
|
+
"sshHost": "<repo-name>-devcontainer",
|
|
125
|
+
"id": "<uuid>",
|
|
126
|
+
"source": "desktop"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"trustedHosts": [
|
|
130
|
+
"<repo-name>-devcontainer"
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Boxdown merges by `sshHost`, preserves an existing Claude remote ID, and adds
|
|
136
|
+
the alias to `trustedHosts`. Restart Claude after installing the target so the
|
|
137
|
+
app applies the SSH remote entry.
|
|
138
|
+
|
|
39
139
|
## Proxy Flow
|
|
40
140
|
|
|
41
141
|
When OpenSSH launches `boxdown ssh-proxy`, Boxdown:
|
|
@@ -44,15 +144,136 @@ When OpenSSH launches `boxdown ssh-proxy`, Boxdown:
|
|
|
44
144
|
2. Ensures the per-workspace host key exists.
|
|
45
145
|
3. Reuses a running devcontainer when possible.
|
|
46
146
|
4. Starts the devcontainer when needed.
|
|
47
|
-
5. Runs a throttled coding-agent CLI update preflight inside the
|
|
147
|
+
5. Runs a throttled default coding-agent CLI update preflight inside the
|
|
148
|
+
container.
|
|
48
149
|
6. Runs the container SSH bootstrap runtime.
|
|
49
150
|
7. Bridges OpenSSH to `/usr/sbin/sshd -i` through `docker exec -i`.
|
|
50
151
|
|
|
51
152
|
This does not publish an SSH port. The SSH stream travels through Docker exec.
|
|
52
153
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
154
|
+
Boxdown augments child-process `PATH` with common macOS GUI-missing tool
|
|
155
|
+
locations, including `/usr/local/bin`, Homebrew paths, `~/.docker/bin`, and
|
|
156
|
+
Docker Desktop's bundled CLI directory. This lets GUI apps such as Claude launch
|
|
157
|
+
the SSH proxy through OpenSSH even when their launchd environment cannot
|
|
158
|
+
resolve `docker` by default.
|
|
159
|
+
|
|
160
|
+
The default coding-agent CLI update preflight covers Codex and Claude Code in
|
|
161
|
+
already-running containers, where `postStartCommand` does not necessarily run
|
|
162
|
+
before a new SSH session. OpenCode and Antigravity remain lazy installs through
|
|
163
|
+
their direct `boxdown` commands. The preflight output is routed to stderr so
|
|
164
|
+
stdout remains reserved for SSH traffic.
|
|
165
|
+
|
|
166
|
+
SSH proxy startup uses the same concise progress protocol as `setup` and
|
|
167
|
+
`start`, but routes progress and failure summaries to stderr because stdout is
|
|
168
|
+
reserved for the SSH stream. Use `boxdown ssh-proxy --verbose` only for manual
|
|
169
|
+
debugging.
|
|
170
|
+
|
|
171
|
+
## Local Web Tunnels
|
|
172
|
+
|
|
173
|
+
The SSH proxy supports TCP forwarding, so Boxdown can expose a web server that is
|
|
174
|
+
listening only inside the devcontainer.
|
|
175
|
+
|
|
176
|
+
From the target project directory:
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
boxdown tunnel --port 3030
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
When `--port` is omitted in an interactive terminal, Boxdown prompts for one or
|
|
183
|
+
more port mappings. Pressing Enter accepts the generated devcontainer published
|
|
184
|
+
port when Boxdown can read one from the config, currently `3000`. Non-TTY and
|
|
185
|
+
CI runs keep requiring an explicit `--port`.
|
|
186
|
+
|
|
187
|
+
From another directory:
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
boxdown tunnel --workspace /path/to/project --port 3030
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This starts or reuses the devcontainer, ensures the SSH alias exists, and then
|
|
194
|
+
keeps a foreground SSH tunnel open:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
127.0.0.1:3030 -> localhost:3030
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
While the tunnel is running, host browsers and the Codex in-app browser can open
|
|
201
|
+
`http://localhost:3030/`.
|
|
202
|
+
|
|
203
|
+
Tunnel setup also hides raw devcontainer startup logs by default; pass
|
|
204
|
+
`--verbose` before the foreground tunnel opens when debugging startup.
|
|
205
|
+
|
|
206
|
+
Repeat `--port` for multiple forwards. Use `<local:remote>` when the host port
|
|
207
|
+
should differ from the container port:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
boxdown tunnel --port 3030 --port 8080:3031
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The tunnel targets remote `localhost` rather than `127.0.0.1` so servers that
|
|
214
|
+
bind to the container's IPv6 loopback, such as some Slidev dev sessions, still
|
|
215
|
+
work.
|
|
216
|
+
|
|
217
|
+
Devcontainer `forwardPorts` is an editor hint, not a host listener in the Codex
|
|
218
|
+
desktop app. Docker `runArgs` / `appPort` publishing can also expose services,
|
|
219
|
+
but it requires the server to bind to an externally reachable container address
|
|
220
|
+
such as `0.0.0.0` and may require recreating the container.
|
|
221
|
+
|
|
222
|
+
Codex's own detected-port forwarding may create a random host URL such as
|
|
223
|
+
`http://localhost:52548/`. That is expected, but the generated SSH forward may
|
|
224
|
+
target remote `127.0.0.1:<port>`. If the dev server is only listening on remote
|
|
225
|
+
IPv6 loopback (`[::1]:<port>`), the random forwarded URL can reset even though a
|
|
226
|
+
listener exists. In that case, either use `boxdown tunnel --port <port>`, or
|
|
227
|
+
start the dev server with a host flag such as `--host 0.0.0.0`.
|
|
228
|
+
|
|
229
|
+
## IPv4 and IPv6 Loopback Options
|
|
230
|
+
|
|
231
|
+
The Codex detected-port issue is not an `/etc/hosts` problem. `/etc/hosts` maps
|
|
232
|
+
names such as `localhost` to addresses, but a generated SSH forward that targets
|
|
233
|
+
remote `127.0.0.1:<port>` uses a numeric IP literal. No name lookup happens, so
|
|
234
|
+
changing the container's `localhost` entry would not make `127.0.0.1` reach a
|
|
235
|
+
server that is listening only on `[::1]`.
|
|
236
|
+
|
|
237
|
+
Potential Boxdown-side approaches:
|
|
238
|
+
|
|
239
|
+
1. Inject a Node.js IPv4 preference.
|
|
240
|
+
|
|
241
|
+
Boxdown could add this to the generated devcontainer environment:
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
NODE_OPTIONS=--dns-result-order=ipv4first
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This would likely make Node-based dev servers such as Vite and Slidev resolve
|
|
248
|
+
`localhost` to `127.0.0.1` first, causing Codex's generated
|
|
249
|
+
`127.0.0.1:<port>` forward to work without app-specific flags.
|
|
250
|
+
|
|
251
|
+
This is the smallest built-in fix to evaluate, but it mainly helps Node.js
|
|
252
|
+
tools. It would not normalize loopback behavior for dev servers written in
|
|
253
|
+
other runtimes.
|
|
254
|
+
|
|
255
|
+
2. Run a Boxdown-managed loopback bridge inside the container.
|
|
256
|
+
|
|
257
|
+
A background helper could detect a listener on `[::1]:<port>` where
|
|
258
|
+
`127.0.0.1:<port>` is not listening, then bind `127.0.0.1:<port>` and proxy
|
|
259
|
+
traffic to `[::1]:<port>`.
|
|
260
|
+
|
|
261
|
+
This would be runtime-agnostic and would let Codex's generated
|
|
262
|
+
`127.0.0.1:<port>` forwards work for more tools. The tradeoff is a more
|
|
263
|
+
complex container-side service: it would need port discovery, race handling,
|
|
264
|
+
conflict handling, lifecycle cleanup, and clear diagnostics.
|
|
265
|
+
|
|
266
|
+
3. Keep `boxdown tunnel` as the explicit escape hatch.
|
|
267
|
+
|
|
268
|
+
The current `boxdown tunnel --port <port>` command creates Boxdown's own SSH
|
|
269
|
+
local forward and targets remote `localhost:<port>` rather than remote
|
|
270
|
+
`127.0.0.1:<port>`. That covers dev servers bound to IPv6 loopback,
|
|
271
|
+
IPv4 loopback, or all interfaces, as long as the remote `localhost` name
|
|
272
|
+
resolves to a reachable address for that server.
|
|
273
|
+
|
|
274
|
+
This is reliable and fully under Boxdown's control, but it requires an extra
|
|
275
|
+
foreground command. It is useful as a debugging and fallback path, not a
|
|
276
|
+
fully automatic browser-forwarding experience.
|
|
56
277
|
|
|
57
278
|
## Key Boundary
|
|
58
279
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
boxdown start
|
|
7
7
|
boxdown codex
|
|
8
8
|
boxdown claude
|
|
9
|
-
boxdown cc
|
|
10
9
|
boxdown opencode
|
|
11
10
|
boxdown antigravity
|
|
12
11
|
```
|
|
@@ -16,18 +15,30 @@ boxdown antigravity
|
|
|
16
15
|
```sh
|
|
17
16
|
--workspace <path>
|
|
18
17
|
--recreate
|
|
18
|
+
--verbose
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Use `boxdown setup` when you want to prepare the devcontainer and SSH/app
|
|
22
|
+
integration without opening an interactive shell.
|
|
23
|
+
|
|
21
24
|
`boxdown shell` remains supported as an alias for `boxdown start`, but `start`
|
|
22
25
|
is the canonical command used in help and documentation.
|
|
26
|
+
`boxdown cc` remains supported as an alias for `boxdown claude`, but `claude`
|
|
27
|
+
is the canonical command used in help and documentation.
|
|
23
28
|
|
|
24
29
|
The coding-agent aliases start or reuse the same workspace devcontainer and
|
|
25
30
|
launch the selected CLI directly:
|
|
26
31
|
|
|
27
32
|
- `boxdown codex` launches `codex`.
|
|
28
|
-
- `boxdown claude`
|
|
29
|
-
- `boxdown opencode` launches
|
|
30
|
-
|
|
33
|
+
- `boxdown claude` launches `claude`.
|
|
34
|
+
- `boxdown opencode` installs/updates OpenCode when needed, then launches
|
|
35
|
+
`opencode`.
|
|
36
|
+
- `boxdown antigravity` installs/updates Antigravity when needed, then launches
|
|
37
|
+
`agy`.
|
|
38
|
+
|
|
39
|
+
Container bring-up eagerly installs only Codex and Claude Code. OpenCode and
|
|
40
|
+
Antigravity are lazy installs so projects that do not use them avoid the extra
|
|
41
|
+
disk usage.
|
|
31
42
|
|
|
32
43
|
Pass agent-specific arguments after `--` so Boxdown options stay unambiguous:
|
|
33
44
|
|
|
@@ -42,7 +53,7 @@ boxdown claude -- --continue
|
|
|
42
53
|
3. Generate a Boxdown-owned devcontainer config.
|
|
43
54
|
4. Install or reuse the pinned Dev Containers CLI.
|
|
44
55
|
5. Run `devcontainer up --workspace-folder <repo> --override-config <config>`.
|
|
45
|
-
6. Run container lifecycle hooks, including a best-effort
|
|
56
|
+
6. Run container lifecycle hooks, including a best-effort Codex/Claude CLI refresh.
|
|
46
57
|
7. Print a dynamic port hint when the configured published port is mapped.
|
|
47
58
|
8. Run `devcontainer exec ... bash` to open an interactive shell.
|
|
48
59
|
|
|
@@ -50,6 +61,11 @@ Coding-agent aliases use the same startup flow but skip the port hint, run a
|
|
|
50
61
|
best-effort refresh for the selected agent, and exec the agent binary instead of
|
|
51
62
|
opening `bash`.
|
|
52
63
|
|
|
64
|
+
Startup progress is concise by default. Raw Docker, Dev Containers CLI,
|
|
65
|
+
lifecycle hook, and coding-agent install/update logs are captured; on failure,
|
|
66
|
+
Boxdown prints the failed step and a short output tail. Pass `--verbose` to
|
|
67
|
+
stream the full startup output before the interactive shell or agent takes over.
|
|
68
|
+
|
|
53
69
|
## Terminal Width
|
|
54
70
|
|
|
55
71
|
Some terminal UIs behave poorly when a host terminal reports an extremely wide
|
|
@@ -57,6 +73,12 @@ PTY, especially from embedded terminals. Before opening the shell, Boxdown
|
|
|
57
73
|
clamps interactive terminal width to 120 columns when the reported width is
|
|
58
74
|
larger.
|
|
59
75
|
|
|
76
|
+
Before starting the interactive shell or a coding-agent command, Boxdown also
|
|
77
|
+
checks whether the container recognizes the forwarded `TERM` value. If the
|
|
78
|
+
container does not have terminfo for a host-specific terminal such as Ghostty,
|
|
79
|
+
Boxdown falls back to `xterm-256color` while preserving truecolor support with
|
|
80
|
+
`COLORTERM=truecolor`.
|
|
81
|
+
|
|
60
82
|
Override the default width when you want a wider layout:
|
|
61
83
|
|
|
62
84
|
```sh
|
|
@@ -76,8 +98,26 @@ BOXDOWN_TTY_NORMALIZE=0 boxdown start
|
|
|
76
98
|
Use it when changing create-time settings such as image, features, mounts, or
|
|
77
99
|
Docker `runArgs`.
|
|
78
100
|
|
|
101
|
+
Existing containers created before Boxdown adopted runtime-mounted secrets
|
|
102
|
+
must be recreated once. After recreation, Boxdown-provided secrets remain
|
|
103
|
+
ordinary Bash-session environment variables but are absent from Docker
|
|
104
|
+
container configuration and `docker inspect`.
|
|
105
|
+
|
|
79
106
|
## Port Hint
|
|
80
107
|
|
|
81
108
|
The v1 asset config publishes container port `3000` with a dynamic host port.
|
|
82
109
|
After `up`, Boxdown asks Docker for the mapped host binding and prints the
|
|
83
110
|
result as an HTTP URL when available.
|
|
111
|
+
|
|
112
|
+
For dev servers that choose another port after the container is already running,
|
|
113
|
+
use the SSH tunnel command instead. Interactive terminals can omit `--port` to
|
|
114
|
+
be prompted for the mapping:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
boxdown tunnel
|
|
118
|
+
boxdown tunnel --port 3030
|
|
119
|
+
boxdown tunnel --workspace /path/to/project --port 3030
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This is the preferred path for host browsers and the Codex in-app browser when a
|
|
123
|
+
server is listening on container-local `localhost`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Default Commit Signing Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make new Boxdown environments sign commits through the host SSH agent by default, while retaining an unsigned, warning-only fallback.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A host-side resolver selects one unambiguous public SSH key and snapshots only that key into workspace state. The generated devcontainer config mounts the host agent and the public-key snapshot. A container bootstrap validates a disposable SSH-signed commit and configures global Git for SSH signing or disables it without touching repository-local Git config.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js 24, TypeScript, Bash, Git, OpenSSH, Docker/Dev Containers CLI, Node test runner.
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- Never copy private signing-key material into Boxdown state or the container.
|
|
14
|
+
- Multiple ambiguous agent identities disable signing and warn; never select one arbitrarily.
|
|
15
|
+
- Signing diagnostics are `warn`, never a setup-preflight failure.
|
|
16
|
+
- Never write `commit.gpgsign` in the repository-local `.git/config`.
|
|
17
|
+
- Docker integration is optional and must not pull images or push commits.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Task 1: Resolve and persist a signing plan
|
|
22
|
+
|
|
23
|
+
**Files:**
|
|
24
|
+
|
|
25
|
+
- Create: `src/git-signing.ts`
|
|
26
|
+
- Modify: `src/constants.ts`, `src/paths.ts`, `src/config.ts`
|
|
27
|
+
- Test: `__tests__/app.test.ts`
|
|
28
|
+
|
|
29
|
+
**Interfaces:**
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export type GitSigningReason = 'agent-unavailable' | 'no-identities' | 'ambiguous-identities' | 'configured-key-not-loaded' | 'agent-mount-unavailable'
|
|
33
|
+
export interface GitSigningPlan { enabled: boolean, reason?: GitSigningReason, publicKey?: string, agentSocketSource?: string }
|
|
34
|
+
export function parseSshPublicKey(value: string): string | undefined
|
|
35
|
+
export function selectGitSigningKey(identities: string[], configuredKey?: string, githubKeys?: string[]): { key?: string, reason?: GitSigningReason }
|
|
36
|
+
export function writeGitSigningPublicKey(context: WorkspaceContext, key: string): void
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- [ ] Write focused failing tests for parsing keys without comments, configured-key selection, a unique GitHub-key match, single-key fallback, ambiguous fallback, and config mount generation.
|
|
40
|
+
- [ ] Run `node --import tsx --test --test-name-pattern='git signing|devcontainer config generation' __tests__/app.test.ts` and confirm the new assertions fail because the module and mounts do not exist.
|
|
41
|
+
- [ ] Add state paths and container constants, implement the pure resolver and public-key snapshot, and let `buildGeneratedDevcontainerConfig(context, signingPlan)` add read-only key-state and agent mounts only for enabled plans.
|
|
42
|
+
- [ ] Rerun the focused tests and confirm they pass.
|
|
43
|
+
|
|
44
|
+
### Task 2: Configure and validate container-side Git signing
|
|
45
|
+
|
|
46
|
+
**Files:**
|
|
47
|
+
|
|
48
|
+
- Create: `assets/devcontainer/utils/git-signing-bootstrap.sh`
|
|
49
|
+
- Modify: `assets/devcontainer/utils/git-config-bootstrap.sh`, `assets/devcontainer/hooks/post-create.sh`, `assets/devcontainer/hooks/post-start.sh`
|
|
50
|
+
- Test: `__tests__/app.test.ts`
|
|
51
|
+
|
|
52
|
+
**Interfaces:**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bash /opt/boxdown/devcontainer/utils/git-signing-bootstrap.sh
|
|
56
|
+
# Reads BOXDOWN_GIT_SIGNING_ENABLED and BOXDOWN_GIT_SIGNING_KEY_PATH.
|
|
57
|
+
# Exits 0 in both signed and unsigned fallback modes.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- [ ] Write failing shell-hook tests for successful SSH configuration, absent-agent fallback, removal of incompatible `gpg.program`, and absence of repository-local `commit.gpgsign` writes.
|
|
61
|
+
- [ ] Run the matching `devcontainer git config hooks` tests and confirm the new assertions fail.
|
|
62
|
+
- [ ] Implement a temporary-repository signing probe using `ssh-add -L`, a temporary allowed-signers file, and `git commit --allow-empty`; set global SSH signing only after it succeeds, otherwise set global `commit.gpgsign=false` and print a warning.
|
|
63
|
+
- [ ] Remove unconditional global signing disablement and all post-create local `commit.gpgsign` mutations; call the bootstrap after create and start.
|
|
64
|
+
- [ ] Rerun the hook tests and confirm they pass.
|
|
65
|
+
|
|
66
|
+
### Task 3: Use one signing-aware lifecycle for every command
|
|
67
|
+
|
|
68
|
+
**Files:**
|
|
69
|
+
|
|
70
|
+
- Modify: `src/devcontainer.ts`, `src/main.ts`
|
|
71
|
+
- Test: `__tests__/app.test.ts`
|
|
72
|
+
|
|
73
|
+
**Interfaces:**
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
export async function resolveGitSigningPlan(context: WorkspaceContext): Promise<GitSigningPlan>
|
|
77
|
+
export async function ensureContainerGitSigning(context: WorkspaceContext, plan: GitSigningPlan, options?: ContainerCommandOptions): Promise<void>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- [ ] Write failing command-flow tests proving `startDevcontainer` writes a signing-aware config and invokes the non-fatal container signing refresh for fresh and reused containers.
|
|
81
|
+
- [ ] Run the relevant `CLI execution` and `coding-agent command mapping` tests and confirm they fail on the missing signing lifecycle.
|
|
82
|
+
- [ ] Resolve the plan before writing generated config, pass plan state to the lifecycle script, and call the refresh from shared startup so setup, shell, coding agents, SSH proxy, and tunnels all inherit it.
|
|
83
|
+
- [ ] Ensure refresh failures only warn and do not change the command exit code.
|
|
84
|
+
- [ ] Rerun focused lifecycle tests and confirm they pass.
|
|
85
|
+
|
|
86
|
+
### Task 4: Add doctor/preflight warnings and user documentation
|
|
87
|
+
|
|
88
|
+
**Files:**
|
|
89
|
+
|
|
90
|
+
- Modify: `src/doctor.ts`, `src/main.ts`, `README.md`, `docs/features/setup.md`, `docs/architecture.md`, `docs/features/README.md`
|
|
91
|
+
- Create: `docs/features/commit-signing.md`, `.changeset/<generated-name>.md`
|
|
92
|
+
- Test: `__tests__/app.test.ts`
|
|
93
|
+
|
|
94
|
+
- [ ] Write failing doctor tests for unavailable agent, ambiguous identities, unusable mount, missing GitHub signing registration, and warning-only setup behavior.
|
|
95
|
+
- [ ] Run the `doctor output` and setup-preflight tests and confirm the new checks are absent.
|
|
96
|
+
- [ ] Add warning-only host signing checks, including an optional GitHub signing-key query; do not add account mutations or required preflight failures.
|
|
97
|
+
- [ ] Document automatic selection order, unsigned fallback, Docker mount limitations, agent security exposure, and GitHub's one-time separate signing-key registration.
|
|
98
|
+
- [ ] Add a changeset for default-on best-effort commit signing.
|
|
99
|
+
- [ ] Rerun focused doctor/setup tests and lint changed Markdown.
|
|
100
|
+
|
|
101
|
+
### Task 5: Verify the full feature
|
|
102
|
+
|
|
103
|
+
**Files:**
|
|
104
|
+
|
|
105
|
+
- Test: `__tests__/app.test.ts`
|
|
106
|
+
|
|
107
|
+
- [ ] Run `node --import tsx --test __tests__/**/*.test.ts`; record the two known unrelated baseline failures and require no additional failures.
|
|
108
|
+
- [ ] Run `npm run build` and `npm run lint` using local project tooling where available.
|
|
109
|
+
- [ ] With Docker and a loaded agent available, create a disposable container using the generated mount, make an empty signed commit, and verify its SSH fingerprint; do not push it.
|
|
110
|
+
- [ ] Review `git diff --check`, changed docs, and the implementation against `docs/superpowers/specs/2026-07-11-default-commit-signing-design.md`.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Progress CI Regression Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Restore CI by making progress checklist cleanup mode-independent and updating stale spinner-label source assertions.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Preserve the existing `ProgressReporter` API and output behavior. Move only internal cleanup ahead of rendering guards, then loosen two source-presence assertions while leaving runtime checklist-output coverage intact.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js 24 test runner, `node:assert`, pnpm, ESLint, tsdown
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- Do not change progress APIs, spinner copy, output formatting, or command flow.
|
|
14
|
+
- Use Node.js 24 for tests because the current Node.js 26 host runtime is incompatible with an installed CommonJS test dependency.
|
|
15
|
+
- Commit documentation separately from the implementation, as explicitly requested.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
### Task 1: Commit the approved documentation
|
|
20
|
+
|
|
21
|
+
**Files:**
|
|
22
|
+
|
|
23
|
+
- Create: `docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md`
|
|
24
|
+
- Create: `docs/superpowers/plans/2026-07-11-progress-ci-regression.md`
|
|
25
|
+
|
|
26
|
+
**Interfaces:**
|
|
27
|
+
|
|
28
|
+
- Consumes: The user-approved diagnosis and design.
|
|
29
|
+
- Produces: Durable design and execution context for the CI fix.
|
|
30
|
+
|
|
31
|
+
- [ ] **Step 1: Verify documentation formatting**
|
|
32
|
+
|
|
33
|
+
Run:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git diff --check
|
|
37
|
+
fnm exec --using v24.15.0 node ../../node_modules/markdownlint-cli/markdownlint.js \
|
|
38
|
+
-c .github/.markdownlint.yml -i 'apm_modules/**' -i '.git' -i '__tests__' \
|
|
39
|
+
-i '.github' -i '.changeset' -i 'CODE_OF_CONDUCT.md' -i 'CHANGELOG.md' \
|
|
40
|
+
-i 'node_modules' -i 'dist' '**/**.md'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Expected: PASS with no whitespace or Markdown lint errors.
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 2: Commit documentation only**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git add docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md docs/superpowers/plans/2026-07-11-progress-ci-regression.md
|
|
49
|
+
git commit -m "docs: design progress CI regression fix"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Expected: A commit containing only the design specification and implementation plan.
|
|
53
|
+
|
|
54
|
+
### Task 2: Fix progress cleanup and stale assertions
|
|
55
|
+
|
|
56
|
+
**Files:**
|
|
57
|
+
|
|
58
|
+
- Modify: `src/progress.ts:147`
|
|
59
|
+
- Test: `__tests__/app.test.ts:3080`
|
|
60
|
+
- Test: `__tests__/app.test.ts:3427`
|
|
61
|
+
|
|
62
|
+
**Interfaces:**
|
|
63
|
+
|
|
64
|
+
- Consumes: `ProgressReporter.end()` and `ProgressReporter.isChecklistActive()`.
|
|
65
|
+
- Produces: Mode-independent reporter cleanup without API changes.
|
|
66
|
+
|
|
67
|
+
- [ ] **Step 1: Verify the existing tests reproduce both CI failures**
|
|
68
|
+
|
|
69
|
+
Run:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
fnm exec --using v24.15.0 node --import tsx --test \
|
|
73
|
+
--test-name-pattern='reports whether a checklist is active|hidden command helpers use friendly spinner labels' \
|
|
74
|
+
__tests__/app.test.ts
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Expected: FAIL because checklist state remains active in `none` mode and the SSH label regular expressions require an outdated direct-property expression.
|
|
78
|
+
|
|
79
|
+
- [ ] **Step 2: Make cleanup independent of rendering mode**
|
|
80
|
+
|
|
81
|
+
In `ProgressReporter.end()`, clear internal state immediately after stopping timers:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
this.#steps = []
|
|
85
|
+
this.#renderedStepLineCount = 0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Remove the existing duplicate cleanup after the interactive section terminator.
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 3: Make the source-presence assertions expression-shape independent**
|
|
91
|
+
|
|
92
|
+
Replace the two SSH assertions with:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
assert.match(sshKeySource, /Generating Boxdown SSH identity/)
|
|
96
|
+
assert.match(sshKeySource, /Writing Boxdown SSH public key/)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- [ ] **Step 4: Verify the focused regression tests pass**
|
|
100
|
+
|
|
101
|
+
Run the Step 1 command again.
|
|
102
|
+
|
|
103
|
+
Expected: PASS for both selected tests.
|
|
104
|
+
|
|
105
|
+
- [ ] **Step 5: Run full verification**
|
|
106
|
+
|
|
107
|
+
Run:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
fnm exec --using v24.15.0 node ../../node_modules/c8/bin/c8.js node --import tsx --test __tests__/**/*.test.ts
|
|
111
|
+
fnm exec --using v24.15.0 node ../../node_modules/eslint/bin/eslint.js .
|
|
112
|
+
fnm exec --using v24.15.0 node ../../node_modules/typescript/bin/tsc
|
|
113
|
+
fnm exec --using v24.15.0 node ../../node_modules/tsdown/dist/run.mjs
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Expected: All tests pass, ESLint reports no errors, TypeScript compilation succeeds, and tsdown builds successfully.
|
|
117
|
+
|
|
118
|
+
- [ ] **Step 6: Commit the implementation separately**
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git add src/progress.ts __tests__/app.test.ts
|
|
122
|
+
git commit -m "fix(progress): clear checklist state in every mode"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Expected: A second commit containing only the production fix and updated assertions.
|