https://github.com/discourse/dv
Discourse Vibe - Agent containers for Discourse Development
https://github.com/discourse/dv
Last synced: about 2 months ago
JSON representation
Discourse Vibe - Agent containers for Discourse Development
- Host: GitHub
- URL: https://github.com/discourse/dv
- Owner: discourse
- License: mit
- Created: 2025-08-10T00:54:47.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2026-05-04T02:27:02.000Z (about 2 months ago)
- Last Synced: 2026-05-04T04:23:15.903Z (about 2 months ago)
- Language: Go
- Homepage:
- Size: 869 KB
- Stars: 27
- Watchers: 0
- Forks: 6
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Discourse AI Agent Container
A Docker-based development environment for AI agents with Discourse.
## Overview
This project provides a containerized development environment that includes:
- Discourse development setup
- Essential developer tools (vim, ripgrep)
- Ready-to-use database configuration, fully migrated dev/test databases
- Various AI helpers preinstalled in the image (Claude, Codex, Aider, Gemini)
- Multi-agent container management via `dv` top-level commands (`list`, `new`, `select`, `rename`)
- Plugin bootstrap helpers via `dv new --plugin` and `dv plugin add`
- Embedded Dockerfile managed by the CLI with safe override mechanisms
## Prerequisites
- Docker installed on your system
- Go 1.22+
- Optional: GitHub CLI (`gh`) if you want to use `dv extract`’s default cloning behavior
## Installation
### Using the install script (recommended)
Install the latest release for macOS or Linux with a single command:
```bash
curl -sSfL https://raw.githubusercontent.com/discourse/dv/main/install.sh | sh
```
The script downloads the correct binary for your platform and installs it to `~/.local/bin` (create it if missing). After it finishes, run `dv version` to confirm that the binary is on your `PATH`.
To pin a specific release or control the install location:
```bash
# install a specific tag
curl -sSfL https://raw.githubusercontent.com/discourse/dv/main/install.sh | sh -s -- --version v0.3.0
# install without sudo
curl -sSfL https://raw.githubusercontent.com/discourse/dv/main/install.sh | sh -s -- --install-dir ~/.local/bin
```
You can also set the `DV_INSTALL_DIR` environment variable to change the default target directory. If `~/.local/bin` (or your custom path) isn’t on your `PATH`, add it in your shell profile, e.g. `export PATH="$HOME/.local/bin:$PATH"`.
`dv` automatically checks for updates once per day in the background. When a newer release is published you’ll see a warning; run `dv upgrade` to install it in place without re-running the shell script. Update metadata is cached at `${XDG_CONFIG_HOME}/dv/update-state.json`.
### Build from source
If you’re hacking on `dv`, build the binary directly:
```bash
go build
```
The resulting binary is written to the repository root (run it via `./dv`).
## Quick Start
With `dv` installed (either via the script or `go build`), run the CLI directly from your shell. If you’re using the locally built binary in this repository, replace `dv` with `./dv` in the commands below.
1. Build the Docker image:
```bash
dv build
```
2. Start the container:
```bash
dv start
```
3. Enter the container, or run a one-off command without opening a shell:
```bash
dv enter
# run a single command
dv run -- bin/rails c
```
4. Extract changes from the container (when ready to create a PR):
```bash
dv extract
# or extract changes for a specific plugin (with TAB completion)
dv extract plugin discourse-akismet
```
Optional: manage multiple named containers ("agents") and bootstrap plugin workspaces:
```bash
dv new my_project # create and select a new agent
dv new --plugin discourse-kanban kanban # create an agent with a plugin pre-cloned
dv plugin add discourse-solved # add a plugin to the selected running agent
dv list # show all agents for the selected image
dv select my_project # select an existing agent
dv rename old new # rename an agent
```
## dv Commands
### dv build
Build the Docker image (defaults to tag `ai_agent`).
```bash
dv build [--no-cache] [--build-arg KEY=VAL] [--rm-existing]
```
Notes:
- Uses an embedded `Dockerfile` managed under your XDG config directory. On each build, the CLI ensures the materialized `Dockerfile` matches the embedded version via a SHA file.
- Override precedence:
1) `DV_DOCKERFILE=/absolute/path/to/Dockerfile`
2) `${XDG_CONFIG_HOME}/dv/Dockerfile.local`
3) Embedded default (materialized to `${XDG_CONFIG_HOME}/dv/Dockerfile`)
The command prints which Dockerfile path it used.
- BuildKit/buildx is enabled by default (`docker buildx build --load`). The CLI automatically falls back to legacy `docker build` if buildx is unavailable.
- Opt-out controls: `--classic-build` forces legacy `docker build`, and `--builder NAME` targets a specific buildx builder (remote builders, Docker Build Cloud, etc.).
### dv pull
Pull a published image/tag instead of building locally.
```bash
dv pull [IMAGE_NAME]
```
### dv image
Manage image definitions, workdirs, ports, and Dockerfile sources.
```bash
dv image list
dv image select NAME
dv image show
```
### dv start
Create or start the container for the selected image (no shell).
```bash
dv start [--reset] [--name NAME] [--image NAME] [--host-starting-port N] [--container-port N]
```
Notes:
- Maps host `4201` → container `4200` by default (Ember CLI dev server). Override with flags.
- Performs a pre-flight check and picks the next free port if needed.
### dv stop
Stop the selected or specified container.
```bash
dv stop [--name NAME]
# Restart the container
dv restart [--name NAME]
# Restart only Discourse services (Pitchfork/Sidekiq)
dv restart discourse [--name NAME]
```
### dv reset
Reset the development environment (databases or git state).
```bash
# Reset databases (default behavior)
dv reset [--name NAME]
dv reset db [--name NAME]
# Reset git state (discard local changes, sync with upstream)
dv reset git [--name NAME]
```
Notes for `dv reset` / `dv reset db`:
- Stops Discourse services.
- Resets the development and test databases.
- Runs migrations and seeds test data.
- Restarts services.
Notes for `dv reset git`:
- Discards local code changes in the container.
- Syncs with the upstream branch.
- Reinstalls dependencies and runs migrations.
### dv enter
Attach to the running container as user `discourse` in the workdir and open an interactive shell.
```bash
dv enter [--name NAME]
```
Notes:
- Copies any configured host files into the container before launching the shell (see `copyRules` under config).
### dv run
Run a non-interactive command inside the running container (defaults to the `discourse` user).
```bash
dv run [--name NAME] [--root] -- CMD [ARGS...]
```
Notes:
- Same file-copy behavior as `dv enter`; run `dv run -- ` to execute without opening a shell.
- Pass `--root` to execute as `root` inside the container.
### dv run-agent (alias: ra)
Run an AI agent inside the container with a prompt.
```bash
dv run-agent [--name NAME] AGENT [-- ARGS...|PROMPT ...]
# alias
dv ra codex Write a migration to add foo to users
# interactive mode
dv ra codex
# use a file as the prompt (useful for long instructions)
dv ra codex ./prompts/long-instructions.txt
dv ra codex ~/notes/feature-plan.md
# pass raw args directly to the agent (no prompt wrapping)
dv ra aider -- --yes -m "Refactor widget"
```
Notes:
- Autocompletes common agents: `codex`, `aider`, `claude`, `gemini`, `crush`, `cursor`, `opencode`, `amp`.
- If no prompt is provided, an inline TUI opens for multi-line input (Ctrl+D to run, Esc to cancel).
- You can pass a regular file path as the first argument after the agent (e.g. `dv ra codex ./plan.md`). The file will be read on the host and its contents used as the prompt. If the argument is not a file, the existing prompt behavior is used.
- Filename/path completion is supported when you start typing a path (e.g. `./`, `../`, `/`, or include a path separator).
- Agent invocation is rule-based (no runtime discovery). Use `--` to pass raw args unchanged (e.g., `dv ra codex -- --help`).
### dv mail
Run MailHog and tunnel it to localhost.
```bash
dv mail [--port 8025] [--host-port 8025]
```
Allows you to access MailHog from your browser (e.g., http://localhost:8025) to inspect emails sent by Discourse. Press Ctrl+C to stop the process and the tunnel.
### dv tui
Launch an interactive TUI to manage containers, images, and run commands.
```bash
dv tui
```
### dv import
Push local commits or uncommitted work from the host repository into the running container.
```bash
dv import [--base main]
```
### dv update agents / agent
Refresh the preinstalled AI agents inside the container, either all at once or a single named agent.
```bash
dv update agents [--name NAME]
dv update agent AGENT [--name NAME]
```
Examples:
```bash
dv update agent term-llm
dv update agent codex --name my-container
```
Notes:
- Starts the container if needed before running updates.
- Re-runs the official install scripts or package managers to pull the latest versions.
- Supported single-agent names include `codex`, `gemini`, `crush`, `copilot`, `opencode`, `amp`, `claude`, `aider`, `cursor`, `droid`, `vibe`, and `term-llm`.
### dv remove
Remove the container and optionally the image.
```bash
dv remove [--image] [--name NAME]
```
### Agent management
Manage multiple containers for the selected image; selection is stored in XDG config. These are the preferred top-level commands; the old `dv agent` group has been removed.
```bash
dv list
dv new [NAME]
dv new --plugin discourse-kanban kanban
dv new --plugin discourse/discourse-kanban kanban
dv new --plugin git@github.com:my-org/private-plugin.git private-test
dv select NAME
dv rename OLD NEW
```
`dv new --plugin` creates a normal agent, then clones each requested plugin into the Discourse `plugins/` directory before running provisioning maintenance. `--plugin` is repeatable. Plugin arguments accept:
- `discourse-kanban` shorthand for `https://github.com/discourse/discourse-kanban.git`
- `owner/repo` shorthand for `https://github.com/owner/repo.git`
- full HTTPS URLs
- SSH URLs such as `git@github.com:owner/repo.git`
### dv plugin
Manage plugins in the selected running agent.
```bash
dv plugin list
dv plugin add discourse-kanban
dv plugin add discourse/discourse-kanban
dv plugin add git@github.com:my-org/private-plugin.git
dv plugin add --branch main discourse-kanban
dv plugin add --skip-maintenance discourse-kanban
```
Notes:
- `dv plugin add` clones plugins into `/var/www/discourse/plugins/` by default.
- After cloning, it runs bundle install and database migrations unless `--skip-maintenance` is used.
- SSH plugin URLs require SSH agent forwarding in the target container. `dv new --plugin git@github.com:owner/repo.git ...` enables this for the new agent automatically; for `dv plugin add git@...`, use an agent that was created with SSH forwarding or use an HTTPS URL.
- Use `dv plugin --name NAME add ...` to target a specific running agent.
### Templates
Provision containers with pre-defined configurations using YAML templates. This is useful for setting up specific environments, installing plugins/themes, or applying site settings automatically.
```bash
# Create a new agent from a local template
dv new my-feature --template ./templates/stable.yaml
# Create a new agent from a URL
dv new my-feature --template https://raw.githubusercontent.com/discourse/dv/main/templates/full.yaml
```
A default template can also be set via `dv config defaultTemplate [PATH]`, which will use the provided template at the path if `dv new` is ran without an explicit `--template` flag.
Templates support:
- **Discourse Configuration**: Specify branches, PRs, or custom repos.
- **Plugins & Themes**: Automatically clone plugins and install/watch themes.
- **Site Settings**: Set Discourse settings (title, theme, experimental features) on boot.
- **Copy Rules**: Sync host files (like `.gitconfig` or API keys) into the container.
- **Provisioning**: Run arbitrary bash commands via `on_create`.
- **MCP Servers**: Register Model Context Protocol servers for AI agents.
See [templates/full.yaml](./templates/full.yaml) for a complete example of all available features.
### dv extract
Copy modified files from the running container’s `/var/www/discourse` into a local clone and create a new branch at the container’s HEAD.
```bash
dv extract [--name NAME] [--sync] [--debug]
```
By default, the destination is `${XDG_DATA_HOME}/dv/discourse_src`. When a container uses a custom workdir (for example, a theme under `/home/discourse/winter-colors`), the extract target becomes `${XDG_DATA_HOME}/dv/_src` so each workspace mirrors into its own folder.
`--sync` keeps the container and host codebases synchronized after the initial extract by watching for changes in both environments (press `Ctrl+C` to exit). `--debug` adds verbose logging while in sync mode. These flags cannot be combined with `--chdir` or `--echo-cd`.
Note: sync mode requires `inotifywait` to be available inside the container (included in latest Dockerfile used here).
Examples:
```bash
# Perform a one-off extract
dv extract
# Start continuous two-way sync with verbose logging
dv extract --sync --debug
```
### dv pr
Checkout a GitHub pull request in the container and reset the development environment.
```bash
dv pr [--name NAME] [--no-reset] NUMBER
```
Notes:
- Fetches and checks out the specified PR into a local branch.
- Performs a full database reset and migration (development and test databases).
- Reinstalls dependencies (bundle and pnpm).
- Seeds test users.
- Use `--no-reset` to skip DB drop, create, and seed, but still run migrations reinstall deps.
- Supports TAB completion with PR numbers and titles from GitHub API.
- Only works with containers using the `discourse` image kind.
Examples:
```bash
# Checkout PR #12345
dv pr 12345
# Checkout without resetting DB
dv pr --no-reset 12345
# Use TAB completion to search and select a PR
dv pr
```
### dv branch
Checkout a git branch in the container and reset the development environment.
```bash
dv branch [--name NAME] [--no-reset] [--new] BRANCH
```
Notes:
- Checks out the specified branch and pulls latest changes.
- Performs a full database reset and migration (development and test databases).
- Reinstalls dependencies (bundle and pnpm).
- Seeds test users.
- Use `--no-reset` to skip DB drop, create, and seed, but still run migrations reinstall deps.
- Use `--new` to create a new branch from origin/main (or origin/master) if the branch does not exist on remote.
- Supports TAB completion(e.g., `dv branch me` queries only branches starting with "me").
- Only works with containers using the `discourse` image kind.
Examples:
```bash
# Checkout main branch
dv branch main
# Use TAB completion to list and select a branch
dv branch
# Checkout a feature branch
dv branch feature/my-feature
# Create a new local branch for development
dv branch --new my-new-feature
# Quickly switch branches without resetting DB
dv branch --no-reset main
```
### dv extract plugin
Extract changes for a single plugin from the running container. This is useful when a plugin is its own git repository under `/var/www/discourse/plugins`.
```bash
dv extract plugin [--name NAME] [--chdir] [--echo-cd]
```
Notes:
- Requires the container to be running to discover plugins.
- TAB completion suggests plugin names under `/var/www/discourse/plugins` that are separate git repositories from the core Discourse repo.
- Destination is `${XDG_DATA_HOME}/dv/_src`.
- If the plugin is a git repo with a remote, dv clones it and checks out a branch/commit matching the container; only modified/untracked files are copied over.
- If the plugin has no git remote or isn’t a git repo, dv copies the whole directory to `_src`.
- `--chdir` opens a subshell in the extracted directory on completion. `--echo-cd` prints a `cd ` line to stdout (suitable for `eval`).
Examples:
```bash
# Autocomplete plugin name
dv extract plugin
# Extract changes for akismet plugin
dv extract plugin discourse-akismet
# Jump into the extracted repo afterwards
dv extract plugin discourse-akismet --chdir
# Use in command substitution to cd silently
eval "$(dv extract plugin discourse-akismet --echo-cd)"
```
### dv extract theme
Extract changes for a theme from `/home/discourse` inside the container.
```bash
dv extract theme [--name NAME] [--sync] [--debug] [--chdir] [--echo-cd]
```
Notes:
- Requires the container to be running to discover themes.
- TAB completion suggests theme directories under `/home/discourse` that are git repositories.
- Destination is `${XDG_DATA_HOME}/dv/_src`.
- `--sync` enables continuous bidirectional synchronization (press Ctrl+C to stop).
- `--chdir` opens a subshell in the extracted directory on completion. `--echo-cd` prints a `cd ` line to stdout (suitable for `eval`).
Examples:
```bash
# Extract a theme
dv extract theme winter-colors
# Start continuous sync for theme development
dv extract theme winter-colors --sync
# Jump into the extracted repo afterwards
dv extract theme winter-colors --chdir
```
### dv config
Read/write config stored at `${XDG_CONFIG_HOME}/dv/config.json`.
```bash
dv config get KEY
dv config set KEY VALUE
dv config show
```
#### AI Configuration (LLMs)
Use `dv config ai` to launch a TUI for configuring Discourse AI LLM providers (OpenAI, Anthropic, Bedrock, etc.) and models. It automatically detects API keys from your host environment variables.
#### AI Tool Workspace
Use `dv config ai-tool [NAME]` to scaffold a directory under `/home/discourse/ai-tools` for developing custom Discourse AI tools. It includes `tool.yml` (metadata), `script.js` (logic), and `bin/test` / `bin/sync` helpers.
#### Default Template
Use `dv config defaultTemplate [PATH]` to set default template to be used when `dv new` is ran without a `--template` flag. See [templates/full.yaml](./templates/full.yaml) for a complete example of all available features for templates.
#### Theme bootstrap
Use `dv config theme [REPO]` to prepare a theme workspace inside the running container. Running it with no arguments prompts for a name **and** whether you’re building a full theme or component, installs the `discourse_theme` gem, scaffolds a minimal theme under `/home/discourse/`, writes an `AGENTS.md` brief for AI tools, and updates the workdir override so `dv enter` drops you there. Supplying a git URL or `owner/repo` slug clones the existing theme instead of generating a skeleton, while still installing the gem, writing `AGENTS.md`, and configuring the watcher. Each workspace also receives a `theme-watch-` runit service that runs `discourse_theme watch` with an API key that’s automatically bound to the first admin user; restart it anytime with `sv restart theme-watch-` inside the container. Pass `--theme-name` (and optionally `--kind theme|component`) to skip the interactive prompts, and `--verbose` if you want to see every helper command that runs (handy when debugging API key or watcher issues).
#### Site Settings
Use `dv config site_settings FILENAME.yaml` to apply Discourse site settings from a YAML file. Supports 1Password integration via `op://` references for sensitive values.
#### Local proxy (NAME.dv.localhost)
Run `dv config local-proxy` to build and start a small reverse proxy container (`dv-local-proxy` by default) that maps each new agent to `NAME.dv.localhost` instead of host ports like `localhost:4201`. By default, the proxy listens on localhost only (port 80 for HTTP, 2080 for admin API) for security. Use `--public` to bind to all network interfaces. Use `--https` to enable HTTPS on port 443 via a local mkcert certificate (HTTP will redirect to HTTPS). The proxy registers containers as you create/start them and injects hostname env vars so assets resolve correctly. Stop or remove the proxy container to go back to host-port URLs; only containers created while the proxy is running adopt the hostname.
#### Claude Code Router (CCR)
Use `dv config ccr` to bootstrap Claude Code Router presets via OpenRouter/OpenAI rankings.
#### Copying host files before enter/run-agent
Use `copyRules` in your config to copy host files into the container. Each rule sets a host path (supports `~`, env vars, and globs) and a container destination, plus optional `agents` to only copy when that agent is run via `dv run-agent`. Unscoped rules run for `dv enter`/`dv run`; agent-scoped rules skip those commands.
```json
{
"copyRules": [
{ "host": "~/.codex/auth.json", "container": "/home/discourse/.codex/auth.json", "agents": ["codex"] },
{ "host": "~/.gemini/GEMINI.md", "container": "/home/discourse/.gemini/GEMINI.md", "agents": ["gemini"] },
{ "host": "~/.gemini/*.json", "container": "/home/discourse/.gemini/", "agents": ["gemini"] },
{ "host": "~/.gemini/google_account_id", "container": "/home/discourse/google_account_id", "agents": ["gemini"] }
]
}
```
The parent directory inside the container is created if needed, glob patterns are expanded on the host, and ownership is set to `discourse:discourse` so files stay readable by the working user.
### dv data
Print the data directory path (`${XDG_DATA_HOME}/dv`).
```bash
dv data
```
### dv config completion
Generate shell completion scripts (rarely needed). For zsh:
```bash
dv config completion zsh # print to stdout
dv config completion zsh --install # install to ~/.local/share/zsh/site-functions/_dv
```
### dv upgrade
Download and replace the current binary with the latest GitHub release (or a specific tag).
```bash
dv upgrade # install the newest release for your platform
dv upgrade --version v0.3.0
```
The command writes the data to the same path as the running executable, so use `sudo dv upgrade` if `dv` lives somewhere like `/usr/local/bin`.
## Environment Variables
Automatically passed through when set on the host:
- `CURSOR_API_KEY`
- `MISTRAL_API_KEY`
- `ANTHROPIC_API_KEY`
- `OPENAI_API_KEY`
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_REGION`
- `CLAUDE_CODE_USE_BEDROCK`
- `DEEPSEEK_API_KEY`
- `GEMINI_API_KEY`
- `AMP_API_KEY`
- `GH_TOKEN`
- `OPENROUTER_API_KEY`
- `FACTORY_API_KEY`
- `ANTHROPIC_DEFAULT_SONNET_MODEL`
- `ANTHROPIC_DEFAULT_OPUS_MODEL`
- `ANTHROPIC_DEFAULT_HAIKU_MODEL`
### Build acceleration toggles
Set these on the host to change how `dv build` (and other build helpers) behave:
- `DV_DISABLE_BUILDX` — force legacy `docker build` even if buildx is available.
- `DV_BUILDX_BUILDER` (or `DV_BUILDER`) — default builder name used for `docker buildx build`, useful for remote builders.
## Container Details
The image is based on `discourse/discourse_dev:release` and includes:
- Full Discourse development environment at `/var/www/discourse`
- Ruby/Rails stack with bundled dependencies
- Node.js (pnpm) + Ember CLI dev server
- Databases created and migrated for dev/test
- Development tools (vim, ripgrep)
- Helper tools installed for code agents
- Playwright and system deps preinstalled
## Logs
Runit services log to the following locations inside the container:
| Service | Log Path |
|------------|---------------------------------------|
| pitchfork | `/var/www/discourse/log/unicorn.log` |
| ember-cli | `/var/www/discourse/log/ember-cli.log`|
| caddy | `/var/log/caddy.log` |
| postgresql | `/var/log/postgres/current` |
| redis | `/var/log/redis/current` |
View logs with:
```bash
dv run -- tail -f /var/www/discourse/log/unicorn.log
dv run -- tail -f /var/www/discourse/log/ember-cli.log
dv run --root -- tail /var/log/caddy.log
dv run --root -- tail /var/log/postgres/current
dv run --root -- tail /var/log/redis/current
```
## File Structure
```
.
├── internal/
│ └── assets/
│ ├── Dockerfile # Embedded container definition used by dv build
│ └── dockerfile.go # Embed/resolve logic (env + XDG overrides)
├── cmd/
│ └── dv/ # dv binary entrypoint
├── internal/
│ ├── cli/ # dv subcommands (build, run, stop, ...)
│ ├── config/ # JSON config load/save
│ ├── docker/ # Docker CLI wrappers
│ └── xdg/ # XDG path helpers
├── bin/ # Legacy bash scripts (being replaced by dv)
├── README.md
└── ai-agents.md # Guidance for AI agents contributing here
```
## Development Workflow (using dv)
1. Build image:
```bash
dv build
```
2. Develop inside the container:
```bash
dv start
dv enter
# Work with Discourse at /var/www/discourse
```
3. Extract changes to a local clone and commit:
```bash
dv extract
# For the default Discourse workdir; custom workdirs land in $(dv data)/_src
cd $(dv data)/discourse_src
git add . && git commit -m "Your message"
```
## Releases
This project uses automated GitHub releases with cross-platform binary builds for macOS and Linux.
### Creating a Release
1. **Using the release script** (recommended):
```bash
./scripts/release.sh v1.0.0
# or automatically bump the patch version based on the latest GitHub release
./scripts/release.sh --auto
```
2. **Manual process**:
```bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
### What Happens Automatically
When you push a tag starting with `v`, GitHub Actions will:
1. **Build binaries** for:
- Linux (amd64, arm64)
- macOS (amd64, arm64)
2. **Create a GitHub release** with:
- Release notes from git commits
- Binary downloads for each platform
- Checksums for verification
3. **Archive format**:
- Linux: `.tar.gz`
- macOS: `.tar.gz`
- All platforms include README.md and LICENSE
### Version Information
Check the version of your `dv` binary:
```bash
dv version
```
This will show the version, git commit, and build date.
### Release Configuration
The release process is configured in:
- `.github/workflows/release.yml` - GitHub Actions workflow
- `.goreleaser.yml` - GoReleaser configuration for builds and packaging