boxdown 1.0.0 → 1.2.0
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 +49 -17
- package/assets/devcontainer/devcontainer.json +24 -7
- package/assets/devcontainer/hooks/initialize.sh +32 -0
- package/assets/devcontainer/hooks/post-create.sh +59 -12
- package/assets/devcontainer/hooks/post-start.sh +21 -4
- 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 +56 -0
- package/assets/devcontainer/utils/python-bootstrap.sh +69 -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-Df4E8ARj.cjs +5498 -0
- package/dist/main-XMBsKjIK.mjs +5458 -0
- package/dist/main-XMBsKjIK.mjs.map +1 -0
- package/dist/main.cjs +5 -2
- package/dist/main.d.cts +495 -4
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +495 -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 +23 -0
- package/docs/features/generated-config-and-state.md +57 -4
- package/docs/features/github-auth-refresh.md +12 -0
- package/docs/features/lifecycle.md +97 -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 +40 -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/specs/2026-07-11-default-commit-signing-design.md +396 -0
- package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -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 +68 -10
- package/src/constants.ts +5 -0
- package/src/devcontainer.ts +500 -64
- package/src/doctor.ts +195 -30
- package/src/git-signing.ts +87 -0
- package/src/interactive-prompts.ts +692 -0
- package/src/list.ts +16 -2
- package/src/logging.ts +154 -0
- package/src/main.ts +1213 -63
- package/src/metadata.ts +52 -3
- package/src/package-info.ts +18 -0
- package/src/paths.ts +50 -10
- package/src/process.ts +80 -2
- package/src/progress.ts +593 -0
- package/src/purge.ts +208 -0
- package/src/shell.ts +19 -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/docs/features/README.md
CHANGED
|
@@ -4,10 +4,12 @@ This directory explains Boxdown's user-facing capabilities and the main internal
|
|
|
4
4
|
flows behind them.
|
|
5
5
|
|
|
6
6
|
- [Start command](./start-and-shell.md)
|
|
7
|
+
- [Setup command](./setup.md)
|
|
7
8
|
- [Lifecycle commands](./lifecycle.md)
|
|
8
9
|
- [SSH config and proxy workflow](./ssh-config-and-proxy.md)
|
|
9
10
|
- [GitHub auth refresh](./github-auth-refresh.md)
|
|
10
11
|
- [Generated config and state](./generated-config-and-state.md)
|
|
12
|
+
- [Commit signing](./commit-signing.md)
|
|
11
13
|
|
|
12
14
|
Most features share the same first step: resolve a target workspace, derive
|
|
13
15
|
workspace-specific state paths, generate a devcontainer override config, and
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Commit signing
|
|
2
|
+
|
|
3
|
+
New Boxdown environments attempt SSH commit signing by default. Boxdown forwards
|
|
4
|
+
the host SSH agent, selects a signing identity only when there is one
|
|
5
|
+
unambiguous candidate, and keeps the private key on the host.
|
|
6
|
+
|
|
7
|
+
If the agent is unavailable, has no identities, or has multiple identities
|
|
8
|
+
that Boxdown cannot distinguish, Boxdown warns and configures unsigned commits.
|
|
9
|
+
It never guesses a key and does not block setup or commits.
|
|
10
|
+
|
|
11
|
+
The container can request operations from identities exposed by the forwarded
|
|
12
|
+
agent. Use a dedicated signing identity or an agent that confirms sensitive
|
|
13
|
+
operations when that exposure is unacceptable.
|
|
14
|
+
|
|
15
|
+
GitHub verification is separate from signing. Upload the selected public key as
|
|
16
|
+
a GitHub SSH signing key once to receive the Verified badge:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gh ssh-key add /path/to/signing-key.pub --type signing --title "Boxdown commit signing"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The same key may already be registered for GitHub SSH authentication; GitHub
|
|
23
|
+
requires a second registration with type `signing`.
|
|
@@ -35,6 +35,21 @@ Boxdown-specific override is not set.
|
|
|
35
35
|
SSH private keys live in persistent data. A public-key-only runtime directory is
|
|
36
36
|
used for the container mount.
|
|
37
37
|
|
|
38
|
+
Boxdown-managed lifecycle command output is appended to one per-workspace log
|
|
39
|
+
file:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
~/.local/share/boxdown/workspaces/<workspace-hash>/boxdown.log
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The log is append-only across lifecycle runs. Each run starts a timestamped
|
|
46
|
+
section and records Boxdown-managed command output, Docker/devcontainer child
|
|
47
|
+
process output, and command exit codes. Full interactive shell, agent, and
|
|
48
|
+
tunnel session bytes are not tee'd into the log.
|
|
49
|
+
|
|
50
|
+
The log lives with the workspace's persistent data so `boxdown status` can show
|
|
51
|
+
its path and `boxdown purge` removes it with the rest of the workspace data.
|
|
52
|
+
|
|
38
53
|
Each touched workspace also records inventory metadata at:
|
|
39
54
|
|
|
40
55
|
```text
|
|
@@ -44,16 +59,53 @@ Each touched workspace also records inventory metadata at:
|
|
|
44
59
|
`boxdown list` reads these metadata files as its source of truth, then enriches
|
|
45
60
|
entries with best-effort Docker state.
|
|
46
61
|
|
|
62
|
+
Metadata may also record the last inspected Docker image ID for the workspace so
|
|
63
|
+
`boxdown purge` can remove that exact image even after the container is gone.
|
|
64
|
+
|
|
65
|
+
## External App Config
|
|
66
|
+
|
|
67
|
+
External app integration config is not Boxdown workspace state. Boxdown writes
|
|
68
|
+
it only when an SSH install target is selected from an interactive
|
|
69
|
+
`boxdown setup` or `boxdown ssh install` prompt, or requested explicitly with
|
|
70
|
+
`boxdown setup --target <name>` or `boxdown ssh install --target <name>`.
|
|
71
|
+
Non-interactive runs without `--target` install only the SSH alias and leave
|
|
72
|
+
external app config unchanged.
|
|
73
|
+
|
|
74
|
+
When requested, Boxdown writes Codex's app config at:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
~/.codex/codex-app/config.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`BOXDOWN_CODEX_APP_CONFIG` overrides this path for tests and local development.
|
|
81
|
+
The Codex entry refers to the Boxdown-managed SSH alias and the canonical
|
|
82
|
+
container-side workspace path `/workspaces/<repo-name>`. Codex owns later
|
|
83
|
+
global-state records and sidebar entries it creates from that config, but
|
|
84
|
+
`boxdown ssh uninstall` removes the matching Codex sidebar cache entry when
|
|
85
|
+
unregistering the project.
|
|
86
|
+
|
|
87
|
+
When requested, Boxdown writes Claude's SSH remote config at:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
~/Library/Application Support/Claude/ssh_configs.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`BOXDOWN_CLAUDE_SSH_CONFIGS` overrides this path for tests and local
|
|
94
|
+
development. The Claude entry refers to the Boxdown-managed SSH alias and adds
|
|
95
|
+
that alias to Claude's trusted host list.
|
|
96
|
+
|
|
47
97
|
## Generated Changes
|
|
48
98
|
|
|
49
99
|
Boxdown starts from `assets/devcontainer/devcontainer.json` and rewrites:
|
|
50
100
|
|
|
51
101
|
- `name`, to include the workspace basename.
|
|
52
|
-
- `initializeCommand`, to call the host asset script with the target workspace
|
|
102
|
+
- `initializeCommand`, to call the host asset script with the target workspace
|
|
103
|
+
and host Git config snapshot paths.
|
|
53
104
|
- `postCreateCommand`, to call mounted container assets.
|
|
54
105
|
- `postStartCommand`, to call mounted container assets.
|
|
55
|
-
- `mounts`, to add the read-only asset mount, public-key mount,
|
|
56
|
-
`~/.agents` mount when that directory exists
|
|
106
|
+
- `mounts`, to add the read-only asset mount, public-key mount, host Git config
|
|
107
|
+
snapshot mount, host `~/.agents` mount when that directory exists, and host
|
|
108
|
+
`~/.codex/auth.json` read-only mount when that file exists.
|
|
57
109
|
- `containerEnv`, to point SSH bootstrap at the mounted public key and actual
|
|
58
110
|
container workspace.
|
|
59
111
|
|
|
@@ -61,4 +113,5 @@ The target repository is still the Dev Container workspace via
|
|
|
61
113
|
`--workspace-folder`.
|
|
62
114
|
|
|
63
115
|
Mounts are create-time container settings. Use `boxdown start --recreate` after
|
|
64
|
-
creating or removing host `~/.agents` so Docker
|
|
116
|
+
creating or removing host `~/.agents` or `~/.codex/auth.json` so Docker
|
|
117
|
+
receives the updated mount set.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
```sh
|
|
6
6
|
boxdown refresh-gh-token
|
|
7
7
|
boxdown refresh-gh-token-running
|
|
8
|
+
boxdown refresh-gh-token --verbose
|
|
8
9
|
```
|
|
9
10
|
|
|
10
11
|
Both commands copy host GitHub CLI auth into the container using the host token
|
|
@@ -16,6 +17,9 @@ gh auth token
|
|
|
16
17
|
|
|
17
18
|
They do not start a browser login or device-code flow.
|
|
18
19
|
|
|
20
|
+
The startup and refresh work uses concise progress output by default. Pass
|
|
21
|
+
`--verbose` to stream raw devcontainer and auth command output while debugging.
|
|
22
|
+
|
|
19
23
|
These commands are explicit on purpose. Normal `boxdown start`, coding-agent
|
|
20
24
|
launches, and SSH proxy connections do not copy GitHub credentials into the
|
|
21
25
|
container.
|
|
@@ -48,6 +52,8 @@ container's authenticated `gh` for `git fetch`, `git pull`, and `git push`.
|
|
|
48
52
|
For GitHub remotes, Boxdown:
|
|
49
53
|
|
|
50
54
|
- rewrites fetch and push URLs to `https://github.com/<owner>/<repo>.git`
|
|
55
|
+
- configures container-global GitHub HTTPS credentials through the container's
|
|
56
|
+
`gh` auth store
|
|
51
57
|
- resets inherited `credential.https://github.com.helper` entries locally
|
|
52
58
|
- adds `!gh auth git-credential` as the local GitHub credential helper
|
|
53
59
|
- adds a repository-specific HTTPS self-rewrite so broader host rewrites like
|
|
@@ -57,6 +63,12 @@ For GitHub remotes, Boxdown:
|
|
|
57
63
|
The local Git config changes are written to the workspace repository because the
|
|
58
64
|
host checkout is mounted into the devcontainer.
|
|
59
65
|
|
|
66
|
+
Separately, Boxdown snapshots the host `.gitconfig` into workspace state and
|
|
67
|
+
copies it to a writable `/home/node/.gitconfig` during container creation. That
|
|
68
|
+
container copy is sanitized so tools cloning from directories such as `/tmp` use
|
|
69
|
+
HTTPS GitHub auth and do not inherit host-only helpers, GitHub SSH rewrites, or
|
|
70
|
+
signing programs that are unavailable inside Linux.
|
|
71
|
+
|
|
60
72
|
If host `gh` is missing, logged out, or cannot return a token, the refresh is a
|
|
61
73
|
no-op.
|
|
62
74
|
|
|
@@ -4,33 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
boxdown list
|
|
7
|
+
boxdown list --details
|
|
7
8
|
boxdown list --json
|
|
9
|
+
boxdown list --format json
|
|
8
10
|
boxdown status
|
|
9
11
|
boxdown status --json
|
|
12
|
+
boxdown status --format json
|
|
10
13
|
boxdown stop
|
|
11
14
|
boxdown down
|
|
15
|
+
boxdown purge
|
|
12
16
|
boxdown doctor
|
|
13
17
|
```
|
|
14
18
|
|
|
15
|
-
Workspace-targeting commands accept `--workspace <path>`. `
|
|
16
|
-
`--
|
|
19
|
+
Workspace-targeting commands accept `--workspace <path>`. `down` also accepts
|
|
20
|
+
repeated `--workspace` flags to remove multiple workspace containers in order.
|
|
21
|
+
`purge --workspace` also accepts the `PATH`, `SSH ALIAS`, or unambiguous `REPO`
|
|
22
|
+
value shown by `boxdown list`. `purge` does not accept repeated `--workspace`
|
|
23
|
+
flags. `status` accepts `--alias <name>` so its output can match a custom SSH
|
|
24
|
+
host alias.
|
|
17
25
|
|
|
18
26
|
## List
|
|
19
27
|
|
|
20
28
|
`list` shows every workspace with Boxdown metadata under the data root,
|
|
21
29
|
regardless of the directory where the command is run. Human output includes
|
|
22
|
-
`STATE`, `REPO`, `PATH`,
|
|
30
|
+
`STATE`, `REPO`, `PATH`, and `CONTAINER` columns.
|
|
23
31
|
|
|
24
|
-
`list --
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
`list --details` prints one workspace per block with full copyable `path`,
|
|
33
|
+
`ssh alias`, and `container` values.
|
|
34
|
+
|
|
35
|
+
`list --json` and `list --format json` print the same inventory as structured
|
|
36
|
+
JSON. Docker state is best-effort and includes recorded SSH aliases: if Docker
|
|
37
|
+
is unavailable, entries still print and their container state is `unknown`. If a
|
|
38
|
+
recorded repository path no longer exists, the entry is shown as `missing`.
|
|
39
|
+
Container states come from Docker, so active containers usually show `running`,
|
|
40
|
+
stopped containers can show `exited`, and workspaces with no matching container
|
|
41
|
+
show `absent`.
|
|
28
42
|
|
|
29
43
|
## Status
|
|
30
44
|
|
|
31
45
|
`status` is read-only. It reports the resolved workspace, intended cache/data
|
|
32
|
-
paths, generated config path, SSH key paths, SSH alias, and
|
|
33
|
-
container state without recording the workspace in Boxdown
|
|
46
|
+
paths, generated config path, command log path, SSH key paths, SSH alias, and
|
|
47
|
+
the matching Docker container state without recording the workspace in Boxdown
|
|
48
|
+
metadata.
|
|
34
49
|
|
|
35
50
|
Human output distinguishes intended values from detected state. For example, an
|
|
36
51
|
SSH alias can be shown as a computed default even when the matching Boxdown SSH
|
|
@@ -46,7 +61,7 @@ workspace container is running. Missing setup or a stopped/absent container is
|
|
|
46
61
|
reported in the output and exits nonzero. Installing an SSH alias is optional
|
|
47
62
|
and is not required for a healthy status exit.
|
|
48
63
|
|
|
49
|
-
## Stop and
|
|
64
|
+
## Stop, Down, and Purge
|
|
50
65
|
|
|
51
66
|
`stop` stops the workspace devcontainer when it is running. If the container is
|
|
52
67
|
already stopped or absent, it prints a short message and exits 0.
|
|
@@ -54,11 +69,82 @@ already stopped or absent, it prints a short message and exits 0.
|
|
|
54
69
|
`down` removes the workspace devcontainer with Docker. It does not remove
|
|
55
70
|
Boxdown cache, generated config, data directories, or SSH keys.
|
|
56
71
|
|
|
72
|
+
Lifecycle commands append managed output to the workspace command log under the
|
|
73
|
+
workspace data directory. `--verbose` only controls terminal streaming; it does
|
|
74
|
+
not disable or enable log persistence. Foreground interactive shell, agent, and
|
|
75
|
+
tunnel session bytes are not tee'd into the log.
|
|
76
|
+
|
|
77
|
+
`purge` removes the workspace devcontainer with Docker, force-removes the exact
|
|
78
|
+
Docker image ID Boxdown can inspect or has recorded for the workspace, removes
|
|
79
|
+
Boxdown-managed SSH/Codex/Claude entries for the computed, recorded, and
|
|
80
|
+
provided aliases, and deletes the workspace's Boxdown cache/data directories,
|
|
81
|
+
including the per-workspace command log. It does not delete the target
|
|
82
|
+
repository directory or files inside it.
|
|
83
|
+
|
|
84
|
+
`purge --workspace <value>` first treats `<value>` as a filesystem path. If that
|
|
85
|
+
path does not exist, it looks for an exact `PATH`, then `SSH ALIAS`, then `REPO`
|
|
86
|
+
match in Boxdown metadata. `PATH` and `REPO` are shown by `boxdown list`; use
|
|
87
|
+
`boxdown status`, `boxdown list --details`, or JSON list output for SSH aliases.
|
|
88
|
+
`REPO` must match exactly one workspace; if multiple known workspaces share the
|
|
89
|
+
same repo basename, use `PATH` or `SSH ALIAS`.
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
boxdown purge
|
|
93
|
+
boxdown purge --workspace my-repo-devcontainer
|
|
94
|
+
boxdown purge --workspace my-repo
|
|
95
|
+
boxdown purge --workspace /path/to/my-repo
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To remove multiple workspace containers, repeat `--workspace`:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
boxdown down --workspace /path/to/repo-a --workspace /path/to/repo-b
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When `down` runs without `--workspace` from a directory that is not a known
|
|
105
|
+
Boxdown workspace, interactive terminals show a multi-select list of known
|
|
106
|
+
workspaces with existing repository paths. Non-interactive runs keep the
|
|
107
|
+
current-directory behavior.
|
|
108
|
+
|
|
109
|
+
Batch `down` continues after individual workspace failures, but exits nonzero if
|
|
110
|
+
any requested workspace cannot be resolved or removed.
|
|
111
|
+
|
|
112
|
+
When `purge` runs without `--workspace` from a tracked Boxdown workspace, it
|
|
113
|
+
purges that workspace after confirmation. When it runs without `--workspace`
|
|
114
|
+
from an untracked directory, interactive terminals show a multi-select list of
|
|
115
|
+
all tracked workspaces from `boxdown list`, including `running`, `exited`,
|
|
116
|
+
`absent`, `missing`, and `unknown` states. After selection, `purge` asks for one
|
|
117
|
+
destructive confirmation for the selected batch, then purges each selected
|
|
118
|
+
workspace in order. Batch purge continues after individual workspace failures,
|
|
119
|
+
but exits nonzero if any selected workspace fails.
|
|
120
|
+
|
|
121
|
+
In the interactive purge selector, the focused row color-codes only the state
|
|
122
|
+
token: `running` is green, `exited` is yellow, and `absent`, `missing`, or
|
|
123
|
+
`unknown` are red. These colors are terminal UI affordances only; scripts should
|
|
124
|
+
rely on text or JSON state values.
|
|
125
|
+
|
|
126
|
+
Interactive `purge` runs ask for confirmation before removing devcontainer,
|
|
127
|
+
image, SSH/Codex integration, cache, and data state. Non-interactive purge runs
|
|
128
|
+
do not prompt so existing targeted scripts keep working. Non-interactive
|
|
129
|
+
`purge` from an untracked directory fails safely instead of treating the current
|
|
130
|
+
directory as a workspace.
|
|
131
|
+
|
|
57
132
|
## Doctor
|
|
58
133
|
|
|
59
134
|
`doctor` validates required host prerequisites: Node, Docker CLI, Docker
|
|
60
135
|
daemon access, SSH tools, packaged devcontainer assets, and Boxdown's packaged
|
|
61
|
-
`@devcontainers/cli` dependency.
|
|
136
|
+
`@devcontainers/cli` dependency. It also uses a local Docker image, when one is
|
|
137
|
+
available, to create and immediately remove disposable containers that verify
|
|
138
|
+
Docker can bind-mount the workspace, Boxdown assets, and Boxdown runtime-state
|
|
139
|
+
paths. The probe never pulls an image or starts container code.
|
|
140
|
+
|
|
141
|
+
Known bind-mount/share failures are errors with the affected host path and
|
|
142
|
+
Docker Desktop remediation guidance. If Docker has no local image or the probe
|
|
143
|
+
cannot complete for another reason, doctor reports a warning instead.
|
|
144
|
+
|
|
145
|
+
`boxdown setup` runs the required doctor checks before it prompts, writes
|
|
146
|
+
workspace metadata, creates SSH keys, generates devcontainer configuration, or
|
|
147
|
+
starts Docker. Required failures stop setup; warnings remain non-blocking.
|
|
62
148
|
|
|
63
149
|
GitHub CLI auth is optional. Missing or unauthenticated `gh` is reported as a
|
|
64
150
|
warning rather than a failure.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Setup Command
|
|
2
|
+
|
|
3
|
+
## Command
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
boxdown setup
|
|
7
|
+
boxdown setup --target codex
|
|
8
|
+
boxdown setup --target claude
|
|
9
|
+
boxdown setup --recreate
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`setup` prepares the current workspace for remote tools without opening an
|
|
13
|
+
interactive shell. It accepts:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
--workspace <path>
|
|
17
|
+
--alias <name>
|
|
18
|
+
--recreate
|
|
19
|
+
--target <name>
|
|
20
|
+
--verbose
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Flow
|
|
24
|
+
|
|
25
|
+
1. Resolve the workspace to a real absolute path.
|
|
26
|
+
2. Ensure per-workspace SSH key material exists.
|
|
27
|
+
3. Generate a Boxdown-owned devcontainer config.
|
|
28
|
+
4. Run `devcontainer up --workspace-folder <repo> --override-config <config>`.
|
|
29
|
+
5. Install or update the Boxdown-managed SSH alias.
|
|
30
|
+
6. Optionally install selected SSH targets such as Codex or Claude.
|
|
31
|
+
|
|
32
|
+
`setup` prints plain progress sections by default. Docker, Dev Containers CLI,
|
|
33
|
+
and lifecycle hook output is captured and only summarized if a command fails.
|
|
34
|
+
Pass `--verbose` to stream the raw build and hook logs to the terminal.
|
|
35
|
+
|
|
36
|
+
Boxdown also appends the managed setup output to the workspace command log at:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
~/.local/share/boxdown/workspaces/<workspace-hash>/boxdown.log
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The log is written regardless of `--verbose`; the flag only changes terminal
|
|
43
|
+
streaming.
|
|
44
|
+
|
|
45
|
+
When `--target codex` is provided, Boxdown writes the Codex app config entry for
|
|
46
|
+
the same alias and container-side project path used by:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
boxdown ssh install --target codex
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
When `--target claude` is provided, Boxdown writes the Claude app SSH remote
|
|
53
|
+
entry for the same alias used by:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
boxdown ssh install --target claude
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When no target is provided, Boxdown uses the same optional target prompt as
|
|
60
|
+
`boxdown ssh install`. In non-interactive shells, setup skips target
|
|
61
|
+
registration unless `--target` is provided.
|
|
62
|
+
|
|
63
|
+
`setup` does not open a shell, launch a coding-agent CLI, or keep a tunnel in the
|
|
64
|
+
foreground. Use `boxdown start`, `boxdown codex`, or `boxdown tunnel` for those
|
|
65
|
+
foreground workflows. Those commands log Boxdown-managed startup steps, but do
|
|
66
|
+
not tee full interactive shell, agent, or tunnel session bytes into the log.
|
|
@@ -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
|
|