boxdown 1.0.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/LICENSE +191 -0
- package/README.md +115 -0
- package/assets/devcontainer/README.md +177 -0
- package/assets/devcontainer/devcontainer.json +83 -0
- package/assets/devcontainer/hooks/initialize.sh +55 -0
- package/assets/devcontainer/hooks/post-create.sh +67 -0
- package/assets/devcontainer/hooks/post-start.sh +34 -0
- package/assets/devcontainer/ssh-config-install.sh +172 -0
- package/assets/devcontainer/start.sh +485 -0
- package/assets/devcontainer/utils/codex-cli-update.sh +9 -0
- package/assets/devcontainer/utils/coding-agent-cli-update.sh +360 -0
- package/assets/devcontainer/utils/deps-install.sh +87 -0
- package/assets/devcontainer/utils/ssh-bootstrap.sh +200 -0
- package/dist/bin/cli.cjs +12 -0
- package/dist/bin/cli.d.cts +1 -0
- package/dist/bin/cli.d.mts +1 -0
- package/dist/bin/cli.mjs +14 -0
- package/dist/bin/cli.mjs.map +1 -0
- package/dist/main-BuEptwlL.cjs +1707 -0
- package/dist/main-ZFTrSVgt.mjs +1685 -0
- package/dist/main-ZFTrSVgt.mjs.map +1 -0
- package/dist/main.cjs +7 -0
- package/dist/main.d.cts +24 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.mts +24 -0
- package/dist/main.d.mts.map +1 -0
- package/dist/main.mjs +3 -0
- package/docs/README.md +34 -0
- package/docs/architecture.md +75 -0
- package/docs/conventions.md +29 -0
- package/docs/development.md +59 -0
- package/docs/features/README.md +14 -0
- package/docs/features/generated-config-and-state.md +64 -0
- package/docs/features/github-auth-refresh.md +64 -0
- package/docs/features/lifecycle.md +64 -0
- package/docs/features/ssh-config-and-proxy.md +60 -0
- package/docs/features/start-and-shell.md +83 -0
- package/docs/testing.md +63 -0
- package/docs/todo.md +170 -0
- package/package.json +128 -0
- package/src/bin/cli.ts +9 -0
- package/src/coding-agents.ts +22 -0
- package/src/config.ts +81 -0
- package/src/constants.ts +9 -0
- package/src/devcontainer-cli.ts +57 -0
- package/src/devcontainer.ts +394 -0
- package/src/doctor.ts +139 -0
- package/src/github-git-auth.ts +143 -0
- package/src/jsonc.ts +123 -0
- package/src/list.ts +102 -0
- package/src/main.ts +362 -0
- package/src/metadata.ts +84 -0
- package/src/paths.ts +117 -0
- package/src/process.ts +94 -0
- package/src/shell.ts +60 -0
- package/src/ssh-config.ts +113 -0
- package/src/ssh-key.ts +52 -0
- package/src/status.ts +327 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# GitHub Auth Refresh
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
boxdown refresh-gh-token
|
|
7
|
+
boxdown refresh-gh-token-running
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Both commands copy host GitHub CLI auth into the container using the host token
|
|
11
|
+
from:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
gh auth token
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
They do not start a browser login or device-code flow.
|
|
18
|
+
|
|
19
|
+
These commands are explicit on purpose. Normal `boxdown start`, coding-agent
|
|
20
|
+
launches, and SSH proxy connections do not copy GitHub credentials into the
|
|
21
|
+
container.
|
|
22
|
+
|
|
23
|
+
## refresh-gh-token
|
|
24
|
+
|
|
25
|
+
`refresh-gh-token` starts or reuses the devcontainer, then refreshes GitHub CLI
|
|
26
|
+
auth inside it.
|
|
27
|
+
|
|
28
|
+
Use this when the container might not already be running.
|
|
29
|
+
|
|
30
|
+
## refresh-gh-token-running
|
|
31
|
+
|
|
32
|
+
`refresh-gh-token-running` requires a running devcontainer for the workspace.
|
|
33
|
+
It fails early when no matching running container exists.
|
|
34
|
+
|
|
35
|
+
Use this when you want to refresh auth without accidentally starting a
|
|
36
|
+
container.
|
|
37
|
+
|
|
38
|
+
## Container Work
|
|
39
|
+
|
|
40
|
+
When a host token is available, Boxdown runs inside the container:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
gh auth login --hostname github.com --git-protocol https --with-token --insecure-storage
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It also configures this repository's local Git config so GitHub remotes use the
|
|
47
|
+
container's authenticated `gh` for `git fetch`, `git pull`, and `git push`.
|
|
48
|
+
For GitHub remotes, Boxdown:
|
|
49
|
+
|
|
50
|
+
- rewrites fetch and push URLs to `https://github.com/<owner>/<repo>.git`
|
|
51
|
+
- resets inherited `credential.https://github.com.helper` entries locally
|
|
52
|
+
- adds `!gh auth git-credential` as the local GitHub credential helper
|
|
53
|
+
- adds a repository-specific HTTPS self-rewrite so broader host rewrites like
|
|
54
|
+
`url.git@github.com:.insteadOf=https://github.com/` do not force SSH inside
|
|
55
|
+
the container
|
|
56
|
+
|
|
57
|
+
The local Git config changes are written to the workspace repository because the
|
|
58
|
+
host checkout is mounted into the devcontainer.
|
|
59
|
+
|
|
60
|
+
If host `gh` is missing, logged out, or cannot return a token, the refresh is a
|
|
61
|
+
no-op.
|
|
62
|
+
|
|
63
|
+
`OP_SERVICE_ACCOUNT_TOKEN`, when present, is a 1Password service account token.
|
|
64
|
+
It is not a GitHub token and is not used by `gh` or GitHub Git operations.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Lifecycle Commands
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
boxdown list
|
|
7
|
+
boxdown list --json
|
|
8
|
+
boxdown status
|
|
9
|
+
boxdown status --json
|
|
10
|
+
boxdown stop
|
|
11
|
+
boxdown down
|
|
12
|
+
boxdown doctor
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Workspace-targeting commands accept `--workspace <path>`. `status` also accepts
|
|
16
|
+
`--alias <name>` so its output can match a custom SSH host alias.
|
|
17
|
+
|
|
18
|
+
## List
|
|
19
|
+
|
|
20
|
+
`list` shows every workspace with Boxdown metadata under the data root,
|
|
21
|
+
regardless of the directory where the command is run. Human output includes
|
|
22
|
+
`STATE`, `REPO`, `PATH`, `SSH ALIAS`, and `CONTAINER` columns.
|
|
23
|
+
|
|
24
|
+
`list --json` prints the same inventory as structured JSON. Docker state is
|
|
25
|
+
best-effort: if Docker is unavailable, entries still print and their container
|
|
26
|
+
state is `unknown`. If a recorded repository path no longer exists, the entry is
|
|
27
|
+
shown as `missing`.
|
|
28
|
+
|
|
29
|
+
## Status
|
|
30
|
+
|
|
31
|
+
`status` is read-only. It reports the resolved workspace, intended cache/data
|
|
32
|
+
paths, generated config path, SSH key paths, SSH alias, and the matching Docker
|
|
33
|
+
container state without recording the workspace in Boxdown metadata.
|
|
34
|
+
|
|
35
|
+
Human output distinguishes intended values from detected state. For example, an
|
|
36
|
+
SSH alias can be shown as a computed default even when the matching Boxdown SSH
|
|
37
|
+
config block has not been installed. Existing paths are labeled `exists`,
|
|
38
|
+
missing paths are labeled `missing`, and managed SSH config blocks are labeled
|
|
39
|
+
`installed`, `missing`, or `outdated`. Healthy values are color-coded green and
|
|
40
|
+
missing or unhealthy values are color-coded red.
|
|
41
|
+
|
|
42
|
+
`status --json` prints the same information as structured JSON for scripts.
|
|
43
|
+
`status` exits 0 only when the generated config exists, devcontainer assets
|
|
44
|
+
exist, SSH key material exists, the runtime public key exists, and the
|
|
45
|
+
workspace container is running. Missing setup or a stopped/absent container is
|
|
46
|
+
reported in the output and exits nonzero. Installing an SSH alias is optional
|
|
47
|
+
and is not required for a healthy status exit.
|
|
48
|
+
|
|
49
|
+
## Stop and Down
|
|
50
|
+
|
|
51
|
+
`stop` stops the workspace devcontainer when it is running. If the container is
|
|
52
|
+
already stopped or absent, it prints a short message and exits 0.
|
|
53
|
+
|
|
54
|
+
`down` removes the workspace devcontainer with Docker. It does not remove
|
|
55
|
+
Boxdown cache, generated config, data directories, or SSH keys.
|
|
56
|
+
|
|
57
|
+
## Doctor
|
|
58
|
+
|
|
59
|
+
`doctor` validates required host prerequisites: Node, Docker CLI, Docker
|
|
60
|
+
daemon access, SSH tools, packaged devcontainer assets, and Boxdown's packaged
|
|
61
|
+
`@devcontainers/cli` dependency.
|
|
62
|
+
|
|
63
|
+
GitHub CLI auth is optional. Missing or unauthenticated `gh` is reported as a
|
|
64
|
+
warning rather than a failure.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SSH Config and Proxy Workflow
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
boxdown ssh-config install
|
|
7
|
+
boxdown ssh-proxy
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`boxdown ssh-config` is accepted as a convenience shortcut for
|
|
11
|
+
`boxdown ssh-config install`, but docs use the explicit install form.
|
|
12
|
+
|
|
13
|
+
`ssh-proxy` is primarily an internal command launched by OpenSSH as a
|
|
14
|
+
`ProxyCommand`.
|
|
15
|
+
|
|
16
|
+
## SSH Alias Installation
|
|
17
|
+
|
|
18
|
+
By default, Boxdown creates an alias named:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
<repo-name>-devcontainer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The generated SSH config block includes:
|
|
25
|
+
|
|
26
|
+
- `User node`
|
|
27
|
+
- a per-workspace host identity file
|
|
28
|
+
- `IdentitiesOnly yes`
|
|
29
|
+
- a `ProxyCommand` that runs the current Boxdown installation's built CLI
|
|
30
|
+
|
|
31
|
+
The block is wrapped in Boxdown markers so repeated installs replace the managed
|
|
32
|
+
block instead of duplicating it.
|
|
33
|
+
|
|
34
|
+
`boxdown status` reports whether that Boxdown-managed block is `installed`,
|
|
35
|
+
`missing`, or `outdated`. It only recognizes blocks wrapped in Boxdown's marker
|
|
36
|
+
comments; an unrelated OpenSSH `Host` entry with the same alias is not treated
|
|
37
|
+
as an installed Boxdown alias.
|
|
38
|
+
|
|
39
|
+
## Proxy Flow
|
|
40
|
+
|
|
41
|
+
When OpenSSH launches `boxdown ssh-proxy`, Boxdown:
|
|
42
|
+
|
|
43
|
+
1. Quietly refreshes the SSH config block.
|
|
44
|
+
2. Ensures the per-workspace host key exists.
|
|
45
|
+
3. Reuses a running devcontainer when possible.
|
|
46
|
+
4. Starts the devcontainer when needed.
|
|
47
|
+
5. Runs a throttled coding-agent CLI update preflight inside the container.
|
|
48
|
+
6. Runs the container SSH bootstrap runtime.
|
|
49
|
+
7. Bridges OpenSSH to `/usr/sbin/sshd -i` through `docker exec -i`.
|
|
50
|
+
|
|
51
|
+
This does not publish an SSH port. The SSH stream travels through Docker exec.
|
|
52
|
+
|
|
53
|
+
The coding-agent CLI update preflight covers already-running containers, where
|
|
54
|
+
`postStartCommand` does not necessarily run before a new SSH session. Its output
|
|
55
|
+
is routed to stderr so stdout remains reserved for SSH traffic.
|
|
56
|
+
|
|
57
|
+
## Key Boundary
|
|
58
|
+
|
|
59
|
+
The private key stays on the host. The container receives only the public key,
|
|
60
|
+
mounted from a public-key-only runtime directory.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Start Command
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
boxdown start
|
|
7
|
+
boxdown codex
|
|
8
|
+
boxdown claude
|
|
9
|
+
boxdown cc
|
|
10
|
+
boxdown opencode
|
|
11
|
+
boxdown antigravity
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`start` targets the current directory by default and accepts:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
--workspace <path>
|
|
18
|
+
--recreate
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`boxdown shell` remains supported as an alias for `boxdown start`, but `start`
|
|
22
|
+
is the canonical command used in help and documentation.
|
|
23
|
+
|
|
24
|
+
The coding-agent aliases start or reuse the same workspace devcontainer and
|
|
25
|
+
launch the selected CLI directly:
|
|
26
|
+
|
|
27
|
+
- `boxdown codex` launches `codex`.
|
|
28
|
+
- `boxdown claude` and `boxdown cc` launch `claude`.
|
|
29
|
+
- `boxdown opencode` launches `opencode`.
|
|
30
|
+
- `boxdown antigravity` launches `agy`.
|
|
31
|
+
|
|
32
|
+
Pass agent-specific arguments after `--` so Boxdown options stay unambiguous:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
boxdown claude -- --continue
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Flow
|
|
39
|
+
|
|
40
|
+
1. Resolve the workspace to a real absolute path.
|
|
41
|
+
2. Ensure per-workspace SSH key material exists.
|
|
42
|
+
3. Generate a Boxdown-owned devcontainer config.
|
|
43
|
+
4. Install or reuse the pinned Dev Containers CLI.
|
|
44
|
+
5. Run `devcontainer up --workspace-folder <repo> --override-config <config>`.
|
|
45
|
+
6. Run container lifecycle hooks, including a best-effort coding-agent CLI refresh.
|
|
46
|
+
7. Print a dynamic port hint when the configured published port is mapped.
|
|
47
|
+
8. Run `devcontainer exec ... bash` to open an interactive shell.
|
|
48
|
+
|
|
49
|
+
Coding-agent aliases use the same startup flow but skip the port hint, run a
|
|
50
|
+
best-effort refresh for the selected agent, and exec the agent binary instead of
|
|
51
|
+
opening `bash`.
|
|
52
|
+
|
|
53
|
+
## Terminal Width
|
|
54
|
+
|
|
55
|
+
Some terminal UIs behave poorly when a host terminal reports an extremely wide
|
|
56
|
+
PTY, especially from embedded terminals. Before opening the shell, Boxdown
|
|
57
|
+
clamps interactive terminal width to 120 columns when the reported width is
|
|
58
|
+
larger.
|
|
59
|
+
|
|
60
|
+
Override the default width when you want a wider layout:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
BOXDOWN_TTY_MAX_COLUMNS=180 boxdown start
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Disable the normalization entirely when you want the container shell to use the
|
|
67
|
+
host terminal size unchanged:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
BOXDOWN_TTY_NORMALIZE=0 boxdown start
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Recreate
|
|
74
|
+
|
|
75
|
+
`--recreate` passes `--remove-existing-container` to the Dev Containers CLI.
|
|
76
|
+
Use it when changing create-time settings such as image, features, mounts, or
|
|
77
|
+
Docker `runArgs`.
|
|
78
|
+
|
|
79
|
+
## Port Hint
|
|
80
|
+
|
|
81
|
+
The v1 asset config publishes container port `3000` with a dynamic host port.
|
|
82
|
+
After `up`, Boxdown asks Docker for the mapped host binding and prints the
|
|
83
|
+
result as an HTTP URL when available.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## Standard Checks
|
|
4
|
+
|
|
5
|
+
Run these before opening a PR:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm run lint
|
|
9
|
+
pnpm run build
|
|
10
|
+
pnpm run test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`pnpm run lint` includes ESLint and markdown lint. Documentation under `docs/`
|
|
14
|
+
is intentionally linted.
|
|
15
|
+
|
|
16
|
+
## Unit Test Strategy
|
|
17
|
+
|
|
18
|
+
Unit tests should avoid starting Docker. Prefer pure tests for:
|
|
19
|
+
|
|
20
|
+
- CLI parsing and command aliases.
|
|
21
|
+
- Workspace path resolution and state directory selection.
|
|
22
|
+
- Generated devcontainer config shape.
|
|
23
|
+
- SSH config block creation and idempotent replacement.
|
|
24
|
+
- Lifecycle status and doctor output formatting.
|
|
25
|
+
- Workspace metadata and list output formatting.
|
|
26
|
+
- Safety invariants, such as not packaging `.ssh/` key material.
|
|
27
|
+
|
|
28
|
+
Use temporary directories for workspace and state tests. Do not write to the
|
|
29
|
+
user's real SSH config in unit tests.
|
|
30
|
+
|
|
31
|
+
## Build and CLI Smoke Tests
|
|
32
|
+
|
|
33
|
+
After `pnpm run build`, smoke test the built binary:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
node dist/bin/cli.cjs --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use a dry-run pack check when changing package assets or `package.json.files`:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npm pack --dry-run --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Confirm `assets/devcontainer/**` is included and `.ssh/` is not.
|
|
46
|
+
|
|
47
|
+
## Manual Acceptance
|
|
48
|
+
|
|
49
|
+
Manual Docker acceptance is heavier and should be done intentionally:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
boxdown start --workspace /path/to/repo
|
|
53
|
+
boxdown list
|
|
54
|
+
boxdown list --json
|
|
55
|
+
boxdown status --workspace /path/to/repo
|
|
56
|
+
boxdown status --workspace /path/to/repo --json
|
|
57
|
+
boxdown doctor --workspace /path/to/repo
|
|
58
|
+
boxdown ssh-config install --workspace /path/to/repo
|
|
59
|
+
ssh <repo-name>-devcontainer 'whoami && pwd'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run this from at least two repositories when changing workspace isolation,
|
|
63
|
+
container lookup, SSH config generation, or generated config behavior.
|
package/docs/todo.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Todo and Next Plans
|
|
2
|
+
|
|
3
|
+
This file is a handoff for the next Boxdown implementation session. It assumes
|
|
4
|
+
v1 exists as a CLI-first package that stores reusable devcontainer assets under
|
|
5
|
+
`assets/devcontainer/`, generates per-workspace override config outside target
|
|
6
|
+
repositories, and supports start (with `shell` as an alias), SSH proxy, SSH
|
|
7
|
+
config install, lifecycle status/stop/down/doctor commands, and GitHub auth
|
|
8
|
+
refresh commands.
|
|
9
|
+
|
|
10
|
+
## Current Baseline
|
|
11
|
+
|
|
12
|
+
- Boxdown has no root `.devcontainer/` directory.
|
|
13
|
+
- Reusable devcontainer source lives in `assets/devcontainer/`.
|
|
14
|
+
- Generated config uses the Dev Containers CLI with `--workspace-folder` and
|
|
15
|
+
`--override-config`.
|
|
16
|
+
- Workspace cache state defaults to `~/.cache/boxdown`.
|
|
17
|
+
- Workspace persistent state defaults to `~/.local/share/boxdown`.
|
|
18
|
+
- Host private SSH keys stay on the host; the container receives only a public
|
|
19
|
+
key mount.
|
|
20
|
+
- Lifecycle commands report status, color-code unhealthy status output, check
|
|
21
|
+
host prerequisites, stop containers, and remove containers without deleting
|
|
22
|
+
Boxdown state or SSH keys.
|
|
23
|
+
- Unit tests cover parsing, generated config shape, SSH config block generation,
|
|
24
|
+
lifecycle output formatting, and packaging safety.
|
|
25
|
+
|
|
26
|
+
## Next Implementation Tracks
|
|
27
|
+
|
|
28
|
+
### 1. Manual Acceptance and Runtime Fixes (completed)
|
|
29
|
+
|
|
30
|
+
The real Docker and SSH workflow was tested from linked local `boxdown`
|
|
31
|
+
commands. Runtime fixes from that pass included interactive terminal width
|
|
32
|
+
normalization and command alias cleanup.
|
|
33
|
+
|
|
34
|
+
Acceptance commands:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
boxdown start --workspace ~/projects/repos/gh-cp
|
|
38
|
+
boxdown ssh-config install --workspace ~/projects/repos/gh-cp
|
|
39
|
+
ssh gh-cp-devcontainer 'whoami && pwd'
|
|
40
|
+
|
|
41
|
+
boxdown start --workspace ~/projects/repos/lirantaldotcom
|
|
42
|
+
boxdown ssh-config install --workspace ~/projects/repos/lirantaldotcom
|
|
43
|
+
ssh lirantaldotcom-devcontainer 'whoami && pwd'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Lifecycle Commands and CLI UX (completed)
|
|
47
|
+
|
|
48
|
+
Added practical commands around the existing start flow:
|
|
49
|
+
|
|
50
|
+
- `boxdown status` to show workspace, generated config path, container ID,
|
|
51
|
+
running state, SSH alias, and key paths.
|
|
52
|
+
- `boxdown stop` to stop the workspace container.
|
|
53
|
+
- `boxdown down` to remove the workspace container.
|
|
54
|
+
- `boxdown doctor` to validate Node, Docker, SSH, packaged `@devcontainers/cli`,
|
|
55
|
+
optional `gh`, and asset presence.
|
|
56
|
+
- `boxdown status --json` for machine-readable status output.
|
|
57
|
+
|
|
58
|
+
Lifecycle commands keep destructive behavior explicit and conservative. They do
|
|
59
|
+
not remove generated state or SSH keys.
|
|
60
|
+
|
|
61
|
+
### 3. Editor Stub Support
|
|
62
|
+
|
|
63
|
+
Add an optional command for users who want native VS Code or Cursor Dev
|
|
64
|
+
Containers discovery:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
boxdown init-editor-stub
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The stub should be opt-in and small. It can either create a minimal
|
|
71
|
+
`.devcontainer/devcontainer.json` that delegates to Boxdown-generated config or
|
|
72
|
+
document why native editor discovery still requires a project-local file.
|
|
73
|
+
|
|
74
|
+
Do not make editor stubs part of the default workflow. The v1 invariant is that
|
|
75
|
+
target repositories stay clean unless the user explicitly asks for a file.
|
|
76
|
+
|
|
77
|
+
### 4. Customization and Profiles
|
|
78
|
+
|
|
79
|
+
Design a small user configuration surface for common changes:
|
|
80
|
+
|
|
81
|
+
- Extra forwarded ports.
|
|
82
|
+
- Extra host mounts for agent config directories.
|
|
83
|
+
- Feature toggles for tools such as Snyk, 1Password, Codex CLI, or APM.
|
|
84
|
+
- Image or Node version overrides.
|
|
85
|
+
- Per-project SSH alias defaults.
|
|
86
|
+
|
|
87
|
+
Prefer a Boxdown-owned config file under user config/state locations before
|
|
88
|
+
adding target-repo config. If target-repo config is needed, make precedence and
|
|
89
|
+
security implications explicit.
|
|
90
|
+
|
|
91
|
+
### 5. Dev Container Feature Packaging
|
|
92
|
+
|
|
93
|
+
Investigate moving the container-side OpenSSH bootstrap and runtime setup into a
|
|
94
|
+
proper Dev Container Feature. This could make lifecycle behavior more
|
|
95
|
+
spec-native and reduce reliance on mounted shell assets.
|
|
96
|
+
|
|
97
|
+
Questions to answer:
|
|
98
|
+
|
|
99
|
+
- Should the feature be published to GHCR?
|
|
100
|
+
- Does publishing a feature complicate local development?
|
|
101
|
+
- Which setup should remain as mounted assets for offline or unreleased use?
|
|
102
|
+
|
|
103
|
+
### 6. Asset Sync Workflow
|
|
104
|
+
|
|
105
|
+
Decide whether `gh-cp/.devcontainer` remains the canonical source or whether
|
|
106
|
+
Boxdown now owns the canonical asset copy.
|
|
107
|
+
|
|
108
|
+
If `gh-cp` remains canonical, add a documented sync command or maintainer script
|
|
109
|
+
that copies only tracked files and excludes `.ssh/`. If Boxdown becomes
|
|
110
|
+
canonical, update docs in both repos so future changes happen in the right
|
|
111
|
+
place.
|
|
112
|
+
|
|
113
|
+
### 7. Integration Test Harness
|
|
114
|
+
|
|
115
|
+
Add tests beyond pure unit coverage without making CI fragile:
|
|
116
|
+
|
|
117
|
+
- Fake `docker`, `npm`, `gh`, and `devcontainer` binaries for command argument
|
|
118
|
+
assertions.
|
|
119
|
+
- Temporary HOME, cache, data, and SSH config paths.
|
|
120
|
+
- Golden tests for generated `devcontainer.json`.
|
|
121
|
+
- Optional local-only integration tests that start real containers.
|
|
122
|
+
|
|
123
|
+
Keep real Docker tests out of default CI until they are fast, deterministic, and
|
|
124
|
+
safe for GitHub Actions runners.
|
|
125
|
+
|
|
126
|
+
### 8. Release Polish
|
|
127
|
+
|
|
128
|
+
Before the first serious npm release:
|
|
129
|
+
|
|
130
|
+
- Confirm package contents with `npm pack --dry-run --json`.
|
|
131
|
+
- Confirm `npx boxdown --help` works from a packed tarball.
|
|
132
|
+
- Add examples for common agent workflows.
|
|
133
|
+
- Confirm the changeset summary is user-facing.
|
|
134
|
+
- Consider whether `dist` should be committed or only generated in release CI.
|
|
135
|
+
|
|
136
|
+
## What a Fresh Session Needs to Know
|
|
137
|
+
|
|
138
|
+
Read these files first:
|
|
139
|
+
|
|
140
|
+
- `docs/architecture.md`
|
|
141
|
+
- `docs/features/generated-config-and-state.md`
|
|
142
|
+
- `docs/features/ssh-config-and-proxy.md`
|
|
143
|
+
- `src/config.ts`
|
|
144
|
+
- `src/devcontainer.ts`
|
|
145
|
+
- `src/ssh-config.ts`
|
|
146
|
+
- `src/ssh-key.ts`
|
|
147
|
+
|
|
148
|
+
Confirm these facts before implementation:
|
|
149
|
+
|
|
150
|
+
- Whether real Docker/SSH manual acceptance is allowed in the current session.
|
|
151
|
+
- Which host platforms are in scope for the next release: macOS, Linux, WSL, or
|
|
152
|
+
Windows OpenSSH.
|
|
153
|
+
- Whether Boxdown or `gh-cp` should own canonical devcontainer assets going
|
|
154
|
+
forward.
|
|
155
|
+
- Whether target repositories may ever contain generated files by default.
|
|
156
|
+
- Whether user configuration should live only in Boxdown state or may be read
|
|
157
|
+
from the target repository.
|
|
158
|
+
- Which editor gets first-class support if editor stubs are built.
|
|
159
|
+
- Whether GitHub auth refresh should keep using `--insecure-storage` inside the
|
|
160
|
+
container or offer a more configurable credential strategy.
|
|
161
|
+
|
|
162
|
+
## Suggested Fresh-Session Prompt
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
Continue Boxdown after v1. Read docs/todo.md, docs/architecture.md, and the
|
|
166
|
+
feature docs first. Start with manual Docker and SSH acceptance for two repos,
|
|
167
|
+
then fix any runtime issues while preserving the invariants: no root
|
|
168
|
+
.devcontainer in Boxdown, no .devcontainer copied into target repos, no private
|
|
169
|
+
SSH key mounted into containers, and generated config/state owned by Boxdown.
|
|
170
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boxdown",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Run a reusable devcontainer sandbox for any local project",
|
|
5
|
+
"types": "dist/main.d.mts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"boxdown": "./dist/bin/cli.cjs"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/main.d.mts",
|
|
14
|
+
"default": "./dist/main.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/main.d.cts",
|
|
18
|
+
"default": "./dist/main.cjs"
|
|
19
|
+
},
|
|
20
|
+
"default": "./dist/main.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./dist/*": {
|
|
23
|
+
"types": "./dist/*.d.mts",
|
|
24
|
+
"import": "./dist/*.mjs",
|
|
25
|
+
"require": "./dist/*.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24.0.0",
|
|
30
|
+
"pnpm": ">=11.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devEngines": {
|
|
33
|
+
"packageManager": {
|
|
34
|
+
"name": "pnpm",
|
|
35
|
+
"version": ">=11.0.0",
|
|
36
|
+
"onFail": "download"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"assets",
|
|
41
|
+
"docs",
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"bin"
|
|
45
|
+
],
|
|
46
|
+
"author": {
|
|
47
|
+
"name": "Liran Tal",
|
|
48
|
+
"email": "liran.tal@gmail.com",
|
|
49
|
+
"url": "git+https://github.com/lirantal"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"provenance": true,
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"keywords": [
|
|
57
|
+
"devcontainer",
|
|
58
|
+
"docker",
|
|
59
|
+
"development-environment",
|
|
60
|
+
"ssh",
|
|
61
|
+
"sandbox"
|
|
62
|
+
],
|
|
63
|
+
"homepage": "https://github.com/lirantal/boxdown",
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/lirantal/boxdown/issues"
|
|
66
|
+
},
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "https://github.com/lirantal/boxdown.git"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@devcontainers/cli": "0.84.1"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@changesets/changelog-github": "^0.5.0",
|
|
76
|
+
"@changesets/cli": "^2.27.7",
|
|
77
|
+
"@eslint/js": "^10.0.1",
|
|
78
|
+
"@types/node": "^25.5.0",
|
|
79
|
+
"c8": "^11.0.0",
|
|
80
|
+
"eslint": "^10.2.1",
|
|
81
|
+
"eslint-plugin-n": "^17.24.0",
|
|
82
|
+
"eslint-plugin-security": "^4.0.0",
|
|
83
|
+
"husky": "^9.0.11",
|
|
84
|
+
"lint-staged": "^16.2.7",
|
|
85
|
+
"markdownlint-cli": "0.48.0",
|
|
86
|
+
"tsdown": "^0.21.5",
|
|
87
|
+
"tsx": "^4.19.4",
|
|
88
|
+
"typescript": "^5.5.3",
|
|
89
|
+
"typescript-eslint": "^8.59.0",
|
|
90
|
+
"validate-conventional-commit": "^1.0.4"
|
|
91
|
+
},
|
|
92
|
+
"lint-staged": {
|
|
93
|
+
"**/*.{js,json}": [
|
|
94
|
+
"pnpm run lint:fix"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"c8": {
|
|
98
|
+
"exclude": [
|
|
99
|
+
"dist/**",
|
|
100
|
+
"coverage/**",
|
|
101
|
+
"__tests__/**",
|
|
102
|
+
"**/*.test.ts",
|
|
103
|
+
"**/*.test.js"
|
|
104
|
+
],
|
|
105
|
+
"include": [
|
|
106
|
+
"src/**"
|
|
107
|
+
],
|
|
108
|
+
"reporter": [
|
|
109
|
+
"text",
|
|
110
|
+
"lcov",
|
|
111
|
+
"html"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"scripts": {
|
|
115
|
+
"start": "tsx src/bin/cli.ts",
|
|
116
|
+
"build": "tsc && tsdown",
|
|
117
|
+
"lint": "eslint . && pnpm run lint:markdown",
|
|
118
|
+
"lint:markdown": "markdownlint -c .github/.markdownlint.yml -i 'apm_modules/**' -i '.git' -i '__tests__' -i '.github' -i '.changeset' -i 'CODE_OF_CONDUCT.md' -i 'CHANGELOG.md' -i 'node_modules' -i 'dist' '**/**.md'",
|
|
119
|
+
"lint:markdown:fix": "markdownlint -c .github/.markdownlint.yml -i 'apm_modules/**' -i '.git' -i '__tests__' -i '.github' -i '.changeset' -i 'CODE_OF_CONDUCT.md' -i 'CHANGELOG.md' -i 'node_modules' -i 'dist' '**/**.md' --fix",
|
|
120
|
+
"lint:fix": "eslint . --fix",
|
|
121
|
+
"test": "c8 node --import tsx --test __tests__/**/*.test.ts",
|
|
122
|
+
"test:watch": "c8 node --import tsx --test --watch __tests__/**/*.test.ts",
|
|
123
|
+
"coverage:view": "open coverage/lcov-report/index.html",
|
|
124
|
+
"version": "changeset version",
|
|
125
|
+
"release": "changeset publish",
|
|
126
|
+
"release:create": "changeset && git add .changeset && git commit -m \"chore: release\" && git push origin HEAD --no-verify"
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/bin/cli.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type CodingAgentCli = 'codex' | 'opencode' | 'claude' | 'antigravity'
|
|
2
|
+
export type CodingAgentCommandAlias = CodingAgentCli | 'cc'
|
|
3
|
+
|
|
4
|
+
const CODING_AGENT_ALIASES: Record<CodingAgentCommandAlias, CodingAgentCli> = {
|
|
5
|
+
codex: 'codex',
|
|
6
|
+
opencode: 'opencode',
|
|
7
|
+
claude: 'claude',
|
|
8
|
+
cc: 'claude',
|
|
9
|
+
antigravity: 'antigravity'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function codingAgentFromCommand (command: string): CodingAgentCli | undefined {
|
|
13
|
+
return CODING_AGENT_ALIASES[command as CodingAgentCommandAlias]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function codingAgentBinary (agent: CodingAgentCli): string {
|
|
17
|
+
if (agent === 'antigravity') {
|
|
18
|
+
return 'agy'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return agent
|
|
22
|
+
}
|