Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/Liohtml/agentbox/security/advisories/new
about: Please report security issues privately via GitHub Security Advisories.
26 changes: 26 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# What & why

<!-- What does this PR change, and why? -->

## Scope

<!-- Which module does this touch? Keep PRs scoped to one module/concern
where possible (see CONTRIBUTING.md → Module ownership). -->

- [ ] `src/cli/` — arg parsing, help, config, command router
- [ ] `src/docker/` — image build, container run/exec, mounts
- [ ] `src/session/` — tmux entry script, TTY sizing
- [ ] `assets/` — Dockerfile / Observer
- [ ] `src/types.ts` / `CONTRACT.md` — contract change (explain below!)
- [ ] Docs / CI / meta

## Checklist

- [ ] `npm run typecheck` passes
- [ ] `npm test` passes
- [ ] No build step, no new runtime dependencies (see CONTRIBUTING.md)
- [ ] Contract friction with `CONTRACT.md` / `src/types.ts` is described below (or n/a)

## Notes

<!-- What works, what's stubbed, any contract friction. -->
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
dev-dependencies:
dependency-type: development
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
43 changes: 39 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: ["**"]
branches: [main]
pull_request:

permissions:
Expand All @@ -14,16 +14,22 @@ concurrency:

jobs:
build:
name: typecheck & test (Node 22)
name: typecheck & test (Node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
steps:
- name: Checkout
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup Node 22
- name: Setup Node ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: "22"
node-version: ${{ matrix.node }}
cache: npm

- name: Install dependencies
Expand All @@ -34,3 +40,32 @@ jobs:

- name: Test
run: npm test

pack-smoke:
name: npm pack smoke test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- name: Pack tarball
run: npm pack --pack-destination "$RUNNER_TEMP"

- name: Install tarball into a fresh project and run the CLI
run: |
mkdir -p "$RUNNER_TEMP/consumer"
cd "$RUNNER_TEMP/consumer"
npm init -y >/dev/null
npm install "$RUNNER_TEMP"/liohtml-agentbox-*.tgz
# The installed bin must run without any repo-local dev setup.
npx agentbox --help
npx agentbox doctor || true # doctor exits non-zero without Docker; it must still *run*
npx agentbox doctor 2>&1 | grep -q "agentbox doctor"
75 changes: 75 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Release

on:
push:
tags: ["v*"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.

permissions:
contents: read

jobs:
checks:
name: typecheck, test & tag/version match
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- run: npm run typecheck
- run: npm test
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Tag must match package.json version
run: |
tag="${GITHUB_REF_NAME#v}"
pkg="$(node -p "require('./package.json').version")"
if [ "$tag" != "$pkg" ]; then
echo "::error::tag v$tag does not match package.json version $pkg — refusing to publish a mismatched release."
exit 1
fi

pack-smoke:
name: npm pack smoke test
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci

- name: Pack tarball and run it as an end user would
run: |
npm pack --pack-destination "$RUNNER_TEMP"
mkdir -p "$RUNNER_TEMP/consumer"
cd "$RUNNER_TEMP/consumer"
npm init -y >/dev/null
npm install "$RUNNER_TEMP"/liohtml-agentbox-*.tgz
npx agentbox --help
npx agentbox doctor 2>&1 | grep -q "agentbox doctor"

publish:
name: npm publish (provenance)
needs: pack-smoke
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
121 changes: 121 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Fixed (review follow-up)
- The container no longer runs with `--rm`: a container that exits early
(entry-script crash) now stays around for `docker logs` to inspect instead
of vanishing immediately. `up` removes a stale stopped container before
starting fresh; `down` still removes it explicitly; `waitForSession` now
also gives up early if the container itself exits instead of polling out
the full timeout.
- Reattaching now reports the **running container's actual** `--yolo` state
(via a container label) instead of echoing back the current invocation's
flag, which is ignored on reattach and could previously claim the wrong
thing in either direction.
- A value flag given with no value (e.g. a trailing `--gpus`) is now a hard
error instead of silently being dropped.
- `validateConfig` (and the "workspace must exist" check) now only gate
`up`/`rebuild`; `shell`/`down` stay usable even with a currently-invalid
`MEM`/`CPUS`/workspace. `doctor` now reports every `validateConfig`
problem explicitly instead of a separate, narrower ad hoc port check.
- `~/.claude.json` symlinked to a directory is now a clear error (previously
it silently passed through and Docker mounted a directory).
- The generated entry script's temp directory is verified (owner uid + no
group/other permissions) after creation, closing a race where another
local user could pre-create it to intercept the mounted script.
- Launcher: dropped the shell-based global-`tsx`-on-PATH fallback (it forwarded
raw argv through a shell, letting metacharacters be interpreted on
Windows); `tsx` is a runtime dependency now, so a missing local install is
a clear, actionable error instead.
- CI/release workflows: `persist-credentials: false` on all checkouts; the
release workflow now verifies the pushed tag matches `package.json`'s
version, runs the `npm pack` tarball smoke test as a publish gate, and
disables dependency caching (a compromised cache must never influence what
gets published).
- Dockerfile: `CLAUDE_CODE_VERSION`/`TSX_VERSION` build-arg defaults are now
pinned exact versions instead of `latest`, so a plain rebuild is
reproducible; override the args for an intentional upgrade.

### Fixed
- **Detaching no longer destroys the sandbox.** The container now runs
detached with the entry script as PID 1 and your terminal attaches via
`docker exec`, so `Ctrl+b d` behaves as documented (box keeps running).
Previously the tmux attach client was PID 1: detach killed every agent and
`--rm` deleted the container.
- **Teammates appear as tmux panes again.** The lead is launched with
`--teammate-mode tmux`; since Claude Code v2.1.179 the upstream default is
`in-process`, which silently disabled agentbox's split-pane layout.
- Observer: project/session names are HTML-escaped before rendering
(stored-XSS guard), the server handles bind errors (`EADDRINUSE`) with a
readable message, the observer tmux window keeps crash output visible
(`remain-on-exit`), and dead code was removed.
- CLI: invalid `--port`/`--memory`/`--cpus` values, unknown flags, stray
positionals, and `:` in mount paths fail fast with clear errors instead of
surfacing as cryptic Docker failures after a minutes-long build; a
`--workspace` typo no longer silently creates and mounts an empty
directory.
- `rebuild` refuses to run while the box is up (it used to rebuild the image
and then silently reattach to the old container); reattaching with tuning
flags now warns that they are ignored; stale stopped containers are removed
before `docker run` instead of causing a name-conflict error.
- `down` no longer reports success when `docker rm -f` actually failed.
- `~/.claude.json` edge cases: a directory there is now a clear error (was: a
broken mount), and symlinked configs (dotfile managers) are followed
correctly.
- The generated entry script lives in a per-user `0700` temp subdirectory
instead of a fixed world-shared `/tmp` path (tamper/TOCTOU + collision).
- Launcher: local `tsx` is invoked via `node` directly — no shell, so paths
with spaces work and `cmd.exe` metacharacters can't inject on Windows.
- `doctor` warns when the host uid differs from the container's `agent`
(1000), which breaks writes to the bind mounts.

### Changed
- Dockerfile: `@anthropic-ai/claude-code` and `tsx` versions are overridable
via build args (`CLAUDE_CODE_VERSION`, `TSX_VERSION`) for reproducible
builds.
- Package renamed to the scoped **`@liohtml/agentbox`** for npm publishing
(the bare name `agentbox` is an npm security placeholder). The installed
command is still `agentbox`.
- `tsx` moved from devDependencies to dependencies so the published package
runs via `npx`/global install without extra setup.
- CI: Node 22 + 24 matrix, plus an `npm pack` smoke test that installs the
tarball and runs the CLI as an end user would.

### Added
- **`--yolo` flag**: the Claude lead runs with
`--dangerously-skip-permissions` (teammates inherit it). Opt-in per
invocation, never the default — see README → Security & trust.
- **Persistent tool-cache volumes**: named Docker volumes for `~/.npm`,
`~/.cargo/registry` and pip's cache. The box itself stays disposable
(`down` / `up`'s stale-container cleanup); only downloaded packages
survive.
- **Git identity + `gh` auth forwarding**: `~/.gitconfig` and `~/.config/gh`
are bind-mounted read-only when present, and the `gh` CLI is installed in
the image — agents can commit and open PRs as you. Credentials are never
baked into the image, but note they *are* readable at runtime by anything
running inside the container, same as the existing Claude credential mount.
- **`--gpus` passthrough** to `docker run --gpus` (e.g. `all`, `device=0`).
- `doctor` warns when the local image architecture doesn't match the host
(emulated images are painfully slow under qemu).
- `SECURITY.md`, `CODE_OF_CONDUCT.md`, PR template, issue-template config,
Dependabot config, `RELEASING.md`, `ROADMAP.md`, this changelog.
- Tag-triggered release workflow with npm provenance.

### Removed
- `PUBLISH.md` (one-time bootstrap doc) — replaced by `RELEASING.md`.

## [0.1.0] — 2026-07-29

### Added
- Initial release: `agentbox up | shell | rebuild | doctor | down`.
- Docker image (Ubuntu 24.04 + Node 22 + Python 3 + Rust + Claude Code + tsx),
non-root `agent` user, no baked-in credentials.
- Host bind mounts for workspace + Claude auth reuse (`~/.claude`,
`~/.claude.json`).
- tmux session with Claude lead (Agent Teams) + Observer dashboard window.
- Zero-dependency Observer web server on `127.0.0.1:4317`.
42 changes: 42 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Code of Conduct

This project follows the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).

## Our pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic
status, nationality, personal appearance, race, caste, color, religion, or
sexual identity and orientation.

## Our standards

Examples of behavior that contributes to a positive environment:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes

Examples of unacceptable behavior:

- Sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information without their explicit permission

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported via a GitHub issue (for non-sensitive matters) or, for anything you'd
rather not post publicly, by contacting the maintainer directly:
[@Liohtml](https://github.com/Liohtml). All complaints will be reviewed and
investigated promptly and fairly.

(Security vulnerabilities have their own private channel — see
[SECURITY.md](SECURITY.md); please don't use it for conduct reports.)

For the full text including enforcement guidelines, see the
[Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
Loading
Loading