From 7c5c38d1d10431cdebc9c0825793747c5f028657 Mon Sep 17 00:00:00 2001 From: shrwnsan <38465+shrwnsan@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:01:52 +0800 Subject: [PATCH] feat: add Pi agent support Add Pi coding agent (pi.dev) as an opt-in tool. - Add AGENTBOX_INCLUDE_PI build arg (default: false) - Add --tool pi CLI flag for running Pi instead of Claude - Auto-enables Pi installation when --tool pi is selected - Mounts ~/.pi directory for Pi configuration persistence - Add .env.example template for API key configuration - Update tool error messages to include 'pi' option Co-Authored-By: GLM --- .env.example | 74 +++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 3 ++ Dockerfile | 55 +++++++++++++++++++++++++++-------- README.md | 11 ++++++- agentbox | 78 +++++++++++++++++++++++++++++++++++++++++++++----- config.example | 21 ++++++++++++++ entrypoint.sh | 4 ++- 7 files changed, 225 insertions(+), 21 deletions(-) create mode 100644 .env.example create mode 100644 config.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f86a495 --- /dev/null +++ b/.env.example @@ -0,0 +1,74 @@ +# ~/.agentbox/.env.example +# ============================================================================ +# AgentBox Global Environment Variables +# Copy this file to ~/.agentbox/.env and replace placeholders with actual values +# +# This template is for GLOBAL configuration (~/.agentbox/.env). +# Project-level .env files (/.env) override these settings. +# +# Setup: +# cp ~/.agentbox/.env.example ~/.agentbox/.env +# chmod 600 ~/.agentbox/.env +# +# SECURITY: Never commit the actual .env file with real API keys! +# ============================================================================ + +# Anthropic (Claude) +# Get API key: https://console.anthropic.com/api-keys +# Used by: Claude Code, Pi Agent, OpenCode +ANTHROPIC_AUTH_TOKEN=sk-ant-your-anthropic-api-key-here +ANTHROPIC_BASE_URL=https://api.anthropic.com + +# Claude Code model selection (optional, uses sensible defaults if not set) +ANTHROPIC_MODEL=claude-3-5-sonnet-20241022 +ANTHROPIC_SMALL_FAST_MODEL=claude-3-5-haiku-20241022 +ANTHROPIC_DEFAULT_OPUS_MODEL=claude-3-opus-20250219 +ANTHROPIC_DEFAULT_SONNET_MODEL=claude-3-5-sonnet-20241022 +ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-3-5-haiku-20241022 + +# ============================================================================ +# Docker Host Access (Optional) +# ============================================================================ +# For containers to reach services running on the host, use: +# AGENTBOX_EXTRA_HOSTS=service.example.com:host-gateway +# Multiple entries can be comma-separated + +# ============================================================================ +# Optional: Additional LLM Providers +# ============================================================================ +# Set API keys for any additional providers you use with OpenCode or Pi: +# +# OpenAI (https://platform.openai.com/api-keys) +# OPENAI_API_KEY=sk-... +# +# Google Gemini (https://aistudio.google.com/app/apikey) +# GEMINI_API_KEY=... +# +# Mistral (https://console.mistral.ai/api-keys) +# MISTRAL_API_KEY=... +# +# Groq (https://console.groq.com/keys) +# GROQ_API_KEY=... +# +# Other providers: Cerebras, xAI, OpenRouter, Azure OpenAI, Amazon Bedrock, etc. +# See Pi docs: https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/providers.md +# And OpenCode docs: https://opencode.ai/docs/config/ + +# ============================================================================ +# Tool Configuration +# ============================================================================ +# Claude Code, OpenCode, and Pi auto-mount configuration directories: +# - Claude: ~/.claude +# - OpenCode: ~/.config/opencode and ~/.local/share/opencode +# - Pi: ~/.pi/agent +# +# Authenticate tools inside agentbox: +# - Claude Code: automatic via ANTHROPIC_API_KEY +# - OpenCode: run 'opencode login' for OAuth +# - Pi: auto-uses ANTHROPIC_API_KEY or OPENAI_API_KEY etc. +# +# ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here +# +# SECURITY NOTE: This file is sourced automatically when using agentbox. +# Keep it private: chmod 600 ~/.agentbox/.env +# Never commit real API keys to git. diff --git a/.gitignore b/.gitignore index d2f63d2..2f27225 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ Thumbs.db # Docker .dockerignore.local +# Worktrees +.worktrees/ + # Logs *.log diff --git a/Dockerfile b/Dockerfile index 402124e..63b491e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,21 @@ # AgentBox - Simplified multi-language development environment for Claude FROM debian:trixie +# Optional Java toolchain (default: on for compatibility) +ARG AGENTBOX_INCLUDE_JAVA=true + +# Claude Code channel (stable or latest) +ARG AGENTBOX_CC_CHANNEL=stable + +# Include OpenCode (default: on) +ARG AGENTBOX_INCLUDE_OPENCODE=true + +# Include Pi (default: off) +ARG AGENTBOX_INCLUDE_PI=false + +# Simple traditional Unix-style prompt (default: off for backwards compatibility) +ARG AGENTBOX_SIMPLE_PROMPT=false + # Prevent interactive prompts during installation ENV DEBIAN_FRONTEND=noninteractive ENV LANG=en_US.UTF-8 @@ -33,8 +48,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ # Python build dependencies python3-dev python3-pip python3-venv \ libssl-dev libffi-dev \ - # Java dependencies - default-jdk maven gradle \ + # Java dependencies (conditional) + $(if [ "$AGENTBOX_INCLUDE_JAVA" = "true" ]; then echo "default-jdk maven gradle"; fi) \ # Search tools ripgrep fd-find && \ # Setup locale @@ -111,13 +126,15 @@ RUN bash -c "source $NVM_DIR/nvm.sh && \ yarn \ pnpm" -# Install SDKMAN for Java toolchain management -RUN curl -s "https://get.sdkman.io?rcupdate=false" | bash && \ - echo 'source "$HOME/.sdkman/bin/sdkman-init.sh"' >> ~/.bashrc && \ - echo 'source "$HOME/.sdkman/bin/sdkman-init.sh"' >> ~/.zshrc && \ - bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \ - sdk install java 21.0.9-tem && \ - sdk install gradle" +# Install SDKMAN for Java toolchain management (conditional) +RUN if [ "$AGENTBOX_INCLUDE_JAVA" = "true" ]; then \ + curl -s "https://get.sdkman.io?rcupdate=false" | bash && \ + echo 'source "$HOME/.sdkman/bin/sdkman-init.sh"' >> ~/.bashrc && \ + echo 'source "$HOME/.sdkman/bin/sdkman-init.sh"' >> ~/.zshrc && \ + bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \ + sdk install java 21.0.9-tem && \ + sdk install gradle"; \ + fi # Setup Python tools RUN /home/${USERNAME}/.local/bin/uv tool install black && \ @@ -154,6 +171,11 @@ if [[ -n "$PS1" ]] && command -v stty >/dev/null; then fi EOF +# Simple traditional Unix-style prompt (opt-in: AGENTBOX_SIMPLE_PROMPT=true) +RUN if [ "$AGENTBOX_SIMPLE_PROMPT" = "true" ]; then \ + echo 'PROMPT='"'"'%n@%m:%~ $ '"'"'' >> ~/.zshrc; \ + fi + # Configure git RUN git config --global init.defaultBranch main && \ git config --global pull.rebase false @@ -201,11 +223,20 @@ USER ${USERNAME} # Dockerfile hasn't changed. This ensures fresh installs on explicit rebuilds instead # of relying on unpredictable auto-update timing. ARG BUILD_TIMESTAMP=unknown -RUN curl -fsSL https://claude.ai/install.sh | bash -s stable && \ +RUN curl -fsSL https://claude.ai/install.sh | bash -s ${AGENTBOX_CC_CHANNEL} && \ zsh -i -c 'which claude && claude --version' -RUN curl -fsSL https://opencode.ai/install | bash && \ - zsh -i -c 'which opencode && opencode --version' +RUN if [ "$AGENTBOX_INCLUDE_OPENCODE" = "true" ]; then \ + curl -fsSL https://opencode.ai/install | bash && \ + zsh -i -c 'which opencode && opencode --version'; \ + fi + +RUN if [ "$AGENTBOX_INCLUDE_PI" = "true" ]; then \ + export NVM_DIR="/home/agent/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && \ + npm install -g @mariozechner/pi-coding-agent && \ + zsh -i -c 'which pi && pi --version'; \ + fi # Entrypoint ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/README.md b/README.md index 34c4ecc..b484601 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ No configuration needed - just install either runtime. - claude code: built-in - opencode: built-in +- pi: built-in (opt-in via AGENTBOX_TOOL=pi) - any other agents (copilot CLI, Aider, Cursor CLI...): easily add it yourself using the prompt at [docs/prompts/add-tool.md](docs/prompts/add-tool.md). ### Adding tools @@ -56,6 +57,9 @@ agentbox # Use OpenCode instead of Claude agentbox --tool opencode +# Use Pi instead of Claude (requires AGENTBOX_TOOL=pi in config first) +agentbox --tool pi + # Or set via environment variable AGENTBOX_TOOL=opencode agentbox @@ -96,6 +100,7 @@ Persistent data (survives container removal): History: ~/.agentbox/projects/agentbox-/history/ Claude: ~/.claude OpenCode: ~/.config/opencode and ~/.local/share/opencode + Pi: ~/.pi ``` ## Languages and Tools @@ -108,6 +113,7 @@ The unified container image includes: - **Shell**: Zsh (default) and Bash with common utilities - **Claude CLI**: Pre-installed with per-project authentication - **OpenCode**: Pre-installed as an alternative AI coding tool +- **Pi**: Pre-installed (opt-in via `--tool pi` or config) ## Authenticating to Git or other SCC Providers @@ -184,13 +190,16 @@ Zsh history is preserved in `~/.agentbox/projects//history` Both tools use bind mounts to share authentication across all AgentBox projects: -**Claude CLI**: +**Claude**: - `~/.claude` mounted at `/home/agent/.claude` **OpenCode**: - Config: `~/.config/opencode` mounted at `/home/agent/.config/opencode` - Auth: `~/.local/share/opencode` mounted at `/home/agent/.local/share/opencode` +**Pi**: +- `~/.pi` mounted at `/home/agent/.pi` + ## Advanced Usage ### Running One-Off Commands diff --git a/agentbox b/agentbox index 1cdb48a..fe26026 100755 --- a/agentbox +++ b/agentbox @@ -123,6 +123,11 @@ build_image() { --build-arg GROUP_ID="${group_id}" \ --build-arg USERNAME="agent" \ --build-arg BUILD_TIMESTAMP=${build_timestamp} \ + --build-arg AGENTBOX_INCLUDE_JAVA="${include_java}" \ + --build-arg AGENTBOX_INCLUDE_OPENCODE="${include_opencode}" \ + --build-arg AGENTBOX_INCLUDE_PI="${include_pi}" \ + --build-arg AGENTBOX_CC_CHANNEL="${cc_channel}" \ + --build-arg AGENTBOX_SIMPLE_PROMPT="${simple_prompt}" \ --label "agentbox.hash=${combined_hash}" \ --label "agentbox.version=1.0.0" \ --label "agentbox.built=${build_timestamp}" \ @@ -332,6 +337,16 @@ run_container() { ) log_info "OpenCode configuration and auth mounted" + elif [[ "$tool" == "pi" ]]; then + local pi_dir="${HOME}/.pi" + + mkdir -p "$pi_dir" + + mount_opts+=( + -v "${pi_dir}:/home/agent/.pi" + ) + + log_info "Pi configuration mounted" else local claude_dir="${HOME}/.claude" @@ -370,6 +385,8 @@ run_container() { local tool_cmd if [[ "$tool" == "opencode" ]]; then tool_cmd="opencode" + elif [[ "$tool" == "pi" ]]; then + tool_cmd="pi" else tool_cmd="claude --dangerously-skip-permissions" fi @@ -466,11 +483,14 @@ Usage: Options: -h, --help Show this help message - --tool TOOL Choose tool: claude (default) or opencode + --tool TOOL Choose tool: claude (default), opencode, or pi -p PORT Forward a port from host to container (can be used multiple times) --rebuild Force rebuild of the image --add-dir DIR Mount additional directory (can be used multiple times) --ignore-dot-env Do not load .env files as environment variables (still mounted) + --no-java Exclude Java toolchain for smaller image (default: included) + --no-opencode Exclude OpenCode installation for smaller image (default: included) + --cc-latest Use Claude Code latest channel instead of stable (may be ahead of stable) Commands: shell [--admin] Start interactive shell instead of Claude CLI @@ -481,18 +501,23 @@ Commands: Use 'shell --admin' to get a shell with sudo privileges. Other commands will be executed inside the container. +Configuration: + Config file: ~/.agentbox/config + CLI flags override config file settings. + See config.example for all available options. + Examples: agentbox # Start Claude CLI for current project agentbox --tool opencode # Start OpenCode instead of Claude + agentbox --tool pi # Start Pi instead of Claude agentbox -p 3002 # Forward port 3002 agentbox -p 3002:8080 # Map host port 3002 to container port 8080 agentbox shell # Start interactive shell instead of Claude agentbox shell --admin # Start admin shell with sudo access - agentbox --add-dir ~/1 --add-dir ~/2 # Add dirs with Claude CLI + agentbox --add-dir ~/1 --add-dir ~/2 # Add dirs with Claude CLI agentbox python script.py # Run Python script in container agentbox --rebuild # Force rebuild image agentbox ssh-init # Set up SSH for AgentBox - AGENTBOX_TOOL=opencode agentbox # Use env var to select tool Environment variables (set in ~/.agentbox/.env): AGENTBOX_EXTRA_HOSTS Comma-separated host:ip pairs added to container /etc/hosts @@ -543,8 +568,20 @@ ssh_setup() { log_info "AgentBox will use this directory for all SSH operations." } +# Load config file if exists +load_config() { + local config_file="${HOME}/.agentbox/config" + if [[ -f "$config_file" ]]; then + # Source the config file, allowing it to override defaults + source "$config_file" + fi +} + # Main execution main() { + # Load config file first (before CLI args, so CLI args can override) + load_config + # Parse arguments local force_rebuild=false local shell_mode=false @@ -553,7 +590,12 @@ main() { declare -a ports=() local extra_dirs=() local tool="${AGENTBOX_TOOL:-claude}" - local ignore_dot_env=false + local ignore_dot_env="${AGENTBOX_IGNORE_DOT_ENV:-false}" + local include_java="${AGENTBOX_INCLUDE_JAVA:-true}" + local include_opencode="${AGENTBOX_INCLUDE_OPENCODE:-true}" + local include_pi="${AGENTBOX_INCLUDE_PI:-false}" + local cc_channel="${AGENTBOX_CC_CHANNEL:-stable}" + local simple_prompt="${AGENTBOX_SIMPLE_PROMPT:-false}" while [[ $# -gt 0 ]]; do case "$1" in @@ -586,7 +628,7 @@ main() { --tool) shift if [[ -z "${1:-}" ]]; then - log_error "--tool requires a value (claude or opencode)" + log_error "--tool requires a value (claude, opencode, or pi)" exit 1 fi tool="$1" @@ -596,6 +638,18 @@ main() { ignore_dot_env=true shift ;; + --no-java) + include_java=false + shift + ;; + --no-opencode) + include_opencode=false + shift + ;; + --cc-latest) + cc_channel=latest + shift + ;; shell) shell_mode=true shift @@ -620,11 +674,21 @@ main() { done # Validate tool selection - if [[ "$tool" != "claude" && "$tool" != "opencode" ]]; then - log_error "Invalid tool: $tool (must be 'claude' or 'opencode')" + if [[ "$tool" != "claude" && "$tool" != "opencode" && "$tool" != "pi" ]]; then + log_error "Invalid tool: $tool (must be 'claude', 'opencode', or 'pi')" exit 1 fi + # Auto-enable OpenCode if tool is set to opencode + if [[ "$tool" == "opencode" ]]; then + include_opencode=true + fi + + # Auto-enable Pi if tool is set to pi + if [[ "$tool" == "pi" ]]; then + include_pi=true + fi + # Check runtime RUNTIME=$(check_runtime) log_info "Using container runtime: $RUNTIME" diff --git a/config.example b/config.example new file mode 100644 index 0000000..5330a1a --- /dev/null +++ b/config.example @@ -0,0 +1,21 @@ +#!/bin/bash +# AgentBox configuration +# Copy this file to ~/.agentbox/config to customize defaults + +# Tool selection: claude (default), opencode, or pi +# AGENTBOX_TOOL=pi + +# Include Java toolchain (JDK, Maven, Gradle) - default: true +# AGENTBOX_INCLUDE_JAVA=false + +# Include OpenCode - default: true +# AGENTBOX_INCLUDE_OPENCODE=false + +# Claude Code channel: stable or latest - default: stable +# AGENTBOX_CC_CHANNEL=latest + +# Simple traditional Unix-style prompt (user@host:~) - default: false +# AGENTBOX_SIMPLE_PROMPT=true + +# Ignore .env files - default: false +# AGENTBOX_IGNORE_DOT_ENV=true diff --git a/entrypoint.sh b/entrypoint.sh index 191f1b5..6863374 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -66,9 +66,11 @@ if [ -t 0 ] && [ -t 1 ]; then echo "📁 Project Directory: ${PROJECT_DIR:-unknown}" echo "🐍 Python: $(python3 --version 2>&1 | cut -d' ' -f2) (uv available)" echo "🟢 Node.js: $(node --version 2>/dev/null || echo 'not found')" - echo "☕ Java: $(java -version 2>&1 | head -1 | cut -d'"' -f2 || echo 'not found')" + echo "☕ Java: $(command -v java >/dev/null 2>&1 && java -version 2>&1 | head -1 | cut -d'"' -f2 || echo 'excluded (--no-java)')" if [ "$TOOL" = "opencode" ]; then echo "🤖 OpenCode: $(opencode --version 2>/dev/null || echo 'not found - check installation')" + elif [ "$TOOL" = "pi" ]; then + echo "🤖 Pi: $(pi --version 2>/dev/null || echo 'not found - check installation')" else echo "🤖 Claude CLI: $(claude --version 2>/dev/null || echo 'not found - check installation')" fi