From 00a892d28e43d4631941a7a6bdad686b05498f34 Mon Sep 17 00:00:00 2001 From: binarypie Date: Fri, 23 Jan 2026 10:08:24 -0800 Subject: [PATCH 1/4] Gemini inside a container --- .github/workflows/build-ai-dev.yml | 88 +++++++ .github/workflows/build-nvim-dev.yml | 6 +- .github/workflows/check-package-versions.yml | 9 +- dot_files/ai-dev/Containerfile | 37 +++ dot_files/ai-dev/Justfile | 243 ++++++++++++++++++ dot_files/ai-dev/ai-entrypoint.sh | 42 +++ .../shared/etc/distrobox/distrobox.ini | 2 +- .../shared/usr/share/ublue-os/just/62-ai.just | 115 +++++++++ 8 files changed, 534 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/build-ai-dev.yml create mode 100644 dot_files/ai-dev/Containerfile create mode 100644 dot_files/ai-dev/Justfile create mode 100644 dot_files/ai-dev/ai-entrypoint.sh create mode 100644 system_files/shared/usr/share/ublue-os/just/62-ai.just diff --git a/.github/workflows/build-ai-dev.yml b/.github/workflows/build-ai-dev.yml new file mode 100644 index 0000000..9256c6b --- /dev/null +++ b/.github/workflows/build-ai-dev.yml @@ -0,0 +1,88 @@ +# Build and publish ai-dev container image +# Triggered on changes to dot_files/ai-dev/ or manual dispatch +# Image published to: ghcr.io/binarypie-dev/ai-dev:latest + +name: Build ai-dev Image + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + paths: + - 'dot_files/ai-dev/**' + - '.github/workflows/build-ai-dev.yml' + pull_request: + paths: + - 'dot_files/ai-dev/**' + - '.github/workflows/build-ai-dev.yml' + workflow_dispatch: + schedule: + # Rebuild daily to get updated packages + - cron: '0 6 * * *' + +env: + IMAGE_NAME: ai-dev + IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }} + +jobs: + build: + runs-on: ubuntu-latest-m + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=sha,prefix= + type=ref,event=pr + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: dot_files/ai-dev + file: dot_files/ai-dev/Containerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate build summary + if: github.event_name != 'pull_request' + run: | + echo "## ai-dev Image Built" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Image:** \`${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Usage" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "# Run Claude Code" >> $GITHUB_STEP_SUMMARY + echo "podman run --rm -it --user root --security-opt label=disable \\" >> $GITHUB_STEP_SUMMARY + echo " -e HOST_UID=\$(id -u) -e HOST_GID=\$(id -g) -e HOME=\$HOME \\" >> $GITHUB_STEP_SUMMARY + echo " -v \"\$(pwd):\$(pwd):rw\" -w \"\$(pwd)\" \\" >> $GITHUB_STEP_SUMMARY + echo " ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest claude" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-nvim-dev.yml b/.github/workflows/build-nvim-dev.yml index 6183b09..a14ef4f 100644 --- a/.github/workflows/build-nvim-dev.yml +++ b/.github/workflows/build-nvim-dev.yml @@ -1,6 +1,6 @@ # Build and publish nvim-dev container image # Triggered on changes to dot_files/nvim/ or manual dispatch -# Image published to: ghcr.io/binarypie/nvim-dev:latest +# Image published to: ghcr.io/binarypie-dev/nvim-dev:latest name: Build nvim-dev Image @@ -21,8 +21,8 @@ on: - '.github/workflows/build-nvim-dev.yml' workflow_dispatch: schedule: - # Rebuild weekly to get updated packages - - cron: '0 6 * * 0' + # Rebuild daily to get updated packages + - cron: '0 6 * * *' env: IMAGE_NAME: nvim-dev diff --git a/.github/workflows/check-package-versions.yml b/.github/workflows/check-package-versions.yml index 8ef3aa2..5ef449b 100644 --- a/.github/workflows/check-package-versions.yml +++ b/.github/workflows/check-package-versions.yml @@ -253,13 +253,14 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - BRANCH="${{ steps.info.outputs.branch }}" + TITLE="${{ steps.info.outputs.title }}" - # Check for PR with this branch - EXISTING=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null) || EXISTING="" + # Check for open PR with the same title + EXISTING=$(gh pr list --state open --search "in:title $TITLE" --json number,title \ + --jq ".[] | select(.title == \"$TITLE\") | .number" 2>/dev/null | head -1) || EXISTING="" if [[ -n "$EXISTING" ]]; then - echo "PR #$EXISTING already exists for this update group" + echo "PR #$EXISTING already exists with title: $TITLE" echo "exists=true" >> $GITHUB_OUTPUT else echo "exists=false" >> $GITHUB_OUTPUT diff --git a/dot_files/ai-dev/Containerfile b/dot_files/ai-dev/Containerfile new file mode 100644 index 0000000..689789d --- /dev/null +++ b/dot_files/ai-dev/Containerfile @@ -0,0 +1,37 @@ +# AI Development Environment (Sandboxed Podman Container) +# Pre-built image with Claude Code and Gemini CLI +# +# Build: podman build -t ai-dev . +# Run: podman run --rm -it --user 0:0 --security-opt label=disable \ +# -e HOME=$HOME -v "$(pwd):$(pwd):rw" -w "$(pwd)" localhost/ai-dev + +FROM ghcr.io/binarypie-dev/nvim-dev:latest + +# ============================================================================= +# LAYER 1: Claude Code (native install) +# ============================================================================= +USER linuxbrew +WORKDIR /home/linuxbrew + +RUN curl -fsSL https://claude.ai/install.sh | bash + +# ============================================================================= +# LAYER 2: Gemini CLI via npm +# ============================================================================= +RUN npm install -g @google/gemini-cli + +# ============================================================================= +# LAYER 3: Entrypoint script (sets PATH, execs command) +# ============================================================================= +USER root +COPY ai-entrypoint.sh /usr/local/bin/ai-entrypoint.sh +RUN chmod +x /usr/local/bin/ai-entrypoint.sh + +# In rootless podman, --user 0:0 maps to host UID (no privilege escalation) +ENTRYPOINT ["/usr/local/bin/ai-entrypoint.sh"] +CMD ["bash"] + +# Labels for GitHub Container Registry +LABEL org.opencontainers.image.source="https://github.com/binarypie/hypercube" +LABEL org.opencontainers.image.description="AI development environment with Claude Code and Gemini CLI" +LABEL org.opencontainers.image.licenses="MIT" diff --git a/dot_files/ai-dev/Justfile b/dot_files/ai-dev/Justfile new file mode 100644 index 0000000..835fff9 --- /dev/null +++ b/dot_files/ai-dev/Justfile @@ -0,0 +1,243 @@ +# AI Development Environment (Sandboxed Podman Container) +# Uses entrypoint-based UID/GID mapping for correct permissions + +local_image := "localhost/ai-dev" +remote_image := "ghcr.io/binarypie-dev/ai-dev:latest" + +# Common podman run flags (UID 0 inside = host user in rootless podman) +podman_flags := "--rm -it --user 0:0 --security-opt label=disable" + +# Default recipe - show help +default: + @just --list + +# ============================================================================= +# Image Building +# ============================================================================= + +# Build the container image locally +build: + @echo "Building ai-dev image locally..." + podman build -t {{local_image}} . + @echo "Done! Image: {{local_image}}" + +# Build without cache +build-no-cache: + @echo "Building ai-dev image (no cache)..." + podman build --no-cache -t {{local_image}} . + @echo "Done! Image: {{local_image}}" + +# Pull the remote image +pull: + podman pull {{remote_image}} + +# Push to GHCR (requires: podman login ghcr.io) +push: + podman tag {{local_image}} {{remote_image}} + podman push {{remote_image}} + +# ============================================================================= +# Usage +# ============================================================================= + +# Run Claude Code in the current directory +claude *args: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" + exec podman run {{podman_flags}} \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -w "$(pwd)" \ + {{local_image}} \ + claude {{args}} + +# Run Gemini CLI in the current directory +gemini *args: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run {{podman_flags}} \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + {{local_image}} \ + gemini {{args}} + +# Enter the container interactively +enter: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run {{podman_flags}} \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + {{local_image}} + +# ============================================================================= +# Installation (wrapper scripts to ~/.local/bin) +# ============================================================================= + +# Install wrapper scripts for claude and gemini to ~/.local/bin +install: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.local/bin" + + cat > "$HOME/.local/bin/claude" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -w "$(pwd)" \ + ghcr.io/binarypie-dev/ai-dev:latest \ + claude "$@" + WRAPPER + chmod +x "$HOME/.local/bin/claude" + + cat > "$HOME/.local/bin/gemini" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + ghcr.io/binarypie-dev/ai-dev:latest \ + gemini "$@" + WRAPPER + chmod +x "$HOME/.local/bin/gemini" + + echo "Installed wrapper scripts to ~/.local/bin/claude and ~/.local/bin/gemini" + +# Install wrapper scripts pointing to local image +install-local: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.local/bin" + + cat > "$HOME/.local/bin/claude" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -w "$(pwd)" \ + localhost/ai-dev \ + claude "$@" + WRAPPER + chmod +x "$HOME/.local/bin/claude" + + cat > "$HOME/.local/bin/gemini" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + localhost/ai-dev \ + gemini "$@" + WRAPPER + chmod +x "$HOME/.local/bin/gemini" + + echo "Installed wrapper scripts (local image) to ~/.local/bin/claude and ~/.local/bin/gemini" + +# Remove wrapper scripts from ~/.local/bin +uninstall: + #!/usr/bin/bash + set -euo pipefail + rm -f "$HOME/.local/bin/claude" + rm -f "$HOME/.local/bin/gemini" + echo "Removed wrapper scripts from ~/.local/bin" + +# ============================================================================= +# Setup & Cleanup +# ============================================================================= + +# Full setup: pull image + install wrappers +setup: pull install + @echo "" + @echo "Setup complete! Run 'claude' or 'gemini' to use the AI assistants." + +# Remove local image +clean: + #!/usr/bin/bash + set -euo pipefail + podman rmi {{local_image}} 2>/dev/null && echo "Removed {{local_image}}" || echo "No local image to remove" + +# ============================================================================= +# Testing +# ============================================================================= + +# Test the built image works correctly +test-build: build + @echo "Testing container..." + podman run {{podman_flags}} -e HOME="$HOME" {{local_image}} claude --version + podman run {{podman_flags}} -e HOME="$HOME" {{local_image}} gemini --version + @echo "" + @echo "All tests passed!" + +# Debug: show container environment, paths, and auth state +debug: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + podman run {{podman_flags}} \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + {{local_image}} \ + --ai-dev-debug diff --git a/dot_files/ai-dev/ai-entrypoint.sh b/dot_files/ai-dev/ai-entrypoint.sh new file mode 100644 index 0000000..bc438e2 --- /dev/null +++ b/dot_files/ai-dev/ai-entrypoint.sh @@ -0,0 +1,42 @@ +#!/usr/bin/bash +set -e + +export PATH="/home/linuxbrew/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/linuxbrew/.npm-global/bin:/home/linuxbrew/go/bin:/home/linuxbrew/.cargo/bin:$PATH" + +# Debug mode: print environment and auth state +if [ "$1" = "--ai-dev-debug" ]; then + echo "=== ai-dev debug ===" + echo "uid=$(id -u) gid=$(id -g) user=$(whoami 2>/dev/null || echo unknown)" + echo "HOME=$HOME" + echo "PATH=$PATH" + echo "" + echo "=== TTY ===" + ls -la /dev/pts/ 2>/dev/null || echo "no /dev/pts" + echo "tty: $(tty 2>/dev/null || echo 'not a tty')" + echo "" + echo "=== env (GOOGLE_/GEMINI_/ANTHROPIC_) ===" + env | grep -E "^(GOOGLE_|GEMINI_|ANTHROPIC_)" || echo "(none set)" + echo "" + echo "=== $HOME/.gemini/ ===" + ls -la "$HOME/.gemini/" 2>/dev/null || echo "not found at $HOME/.gemini/" + echo "" + echo "=== $HOME/.claude/ ===" + ls -la "$HOME/.claude/" 2>/dev/null || echo "not found at $HOME/.claude/" + echo "" + echo "=== $HOME/.config/claude/ ===" + ls -la "$HOME/.config/claude/" 2>/dev/null || echo "not found at $HOME/.config/claude/" + echo "" + echo "=== which claude/gemini ===" + which claude 2>/dev/null || echo "claude: not found" + which gemini 2>/dev/null || echo "gemini: not found" + echo "" + echo "=== node os.homedir() ===" + node -e "console.log(require(\"os\").homedir())" 2>/dev/null || echo "node not available" + exit 0 +fi + +if [ $# -eq 0 ]; then + exec bash +else + exec "$@" +fi diff --git a/system_files/shared/etc/distrobox/distrobox.ini b/system_files/shared/etc/distrobox/distrobox.ini index 4741157..4ae2836 100644 --- a/system_files/shared/etc/distrobox/distrobox.ini +++ b/system_files/shared/etc/distrobox/distrobox.ini @@ -2,7 +2,7 @@ # Pre-configured containers for development [nvim-dev] -image=ghcr.io/binarypie/nvim-dev:latest +image=ghcr.io/binarypie-dev/nvim-dev:latest pull=true init=false start_now=false diff --git a/system_files/shared/usr/share/ublue-os/just/62-ai.just b/system_files/shared/usr/share/ublue-os/just/62-ai.just new file mode 100644 index 0000000..f02d880 --- /dev/null +++ b/system_files/shared/usr/share/ublue-os/just/62-ai.just @@ -0,0 +1,115 @@ +# vim: set ft=make : +# Hypercube AI development environment commands + +ai_image := "ghcr.io/binarypie-dev/ai-dev:latest" +ai_podman_flags := "--rm -it --user 0:0 --security-opt label=disable" + +# Run Claude Code in the current directory +ai-claude *args: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" + exec podman run {{ai_podman_flags}} \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -w "$(pwd)" \ + {{ai_image}} \ + claude {{args}} + +# Run Gemini CLI in the current directory +ai-gemini *args: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run {{ai_podman_flags}} \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + {{ai_image}} \ + gemini {{args}} + +# Enter the ai-dev container interactively +ai-dev: + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run {{ai_podman_flags}} \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + {{ai_image}} + +# Install claude and gemini wrapper scripts to ~/.local/bin +ai-setup: + #!/usr/bin/bash + set -euo pipefail + + echo "Pulling ai-dev image..." + podman pull {{ai_image}} + + mkdir -p "$HOME/.local/bin" + + cat > "$HOME/.local/bin/claude" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.claude" "$HOME/.config/claude" + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -w "$(pwd)" \ + ghcr.io/binarypie-dev/ai-dev:latest \ + claude "$@" + WRAPPER + chmod +x "$HOME/.local/bin/claude" + + cat > "$HOME/.local/bin/gemini" << 'WRAPPER' + #!/usr/bin/bash + set -euo pipefail + mkdir -p "$HOME/.gemini" + env_flags="" + for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" + done + exec podman run --rm -it \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + ghcr.io/binarypie-dev/ai-dev:latest \ + gemini "$@" + WRAPPER + chmod +x "$HOME/.local/bin/gemini" + + echo "" + echo "Setup complete! You can now run 'claude' and 'gemini' directly." + +# Upgrade ai-dev to latest image +ai-upgrade: + #!/usr/bin/bash + set -euo pipefail + echo "Pulling latest ai-dev image..." + podman pull {{ai_image}} + echo "Done! Latest image pulled." From 685007ec91a96629435b6082094241287fd02b05 Mon Sep 17 00:00:00 2001 From: binarypie Date: Fri, 23 Jan 2026 10:51:44 -0800 Subject: [PATCH 2/4] Claude works --- dot_files/ai-dev/Justfile | 153 +++++++++--------- dot_files/ai-dev/ai-entrypoint.sh | 26 ++- .../shared/usr/share/ublue-os/just/62-ai.just | 80 +++++---- 3 files changed, 132 insertions(+), 127 deletions(-) diff --git a/dot_files/ai-dev/Justfile b/dot_files/ai-dev/Justfile index 835fff9..b271116 100644 --- a/dot_files/ai-dev/Justfile +++ b/dot_files/ai-dev/Justfile @@ -5,7 +5,7 @@ local_image := "localhost/ai-dev" remote_image := "ghcr.io/binarypie-dev/ai-dev:latest" # Common podman run flags (UID 0 inside = host user in rootless podman) -podman_flags := "--rm -it --user 0:0 --security-opt label=disable" +podman_flags := "--rm -it --init --user 0:0 --security-opt label=disable" # Default recipe - show help default: @@ -44,12 +44,13 @@ push: claude *args: #!/usr/bin/bash set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" + mkdir -p "$HOME/.claude" + touch "$HOME/.claude.json" exec podman run {{podman_flags}} \ -e HOME="$HOME" \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ -w "$(pwd)" \ {{local_image}} \ claude {{args}} @@ -76,7 +77,8 @@ gemini *args: enter: #!/usr/bin/bash set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + mkdir -p "$HOME/.claude" "$HOME/.gemini" + touch "$HOME/.claude.json" env_flags="" for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do env_flags="$env_flags -e $var" @@ -86,7 +88,7 @@ enter: $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ -v "$HOME/.gemini:$HOME/.gemini:rw" \ -w "$(pwd)" \ {{local_image}} @@ -100,45 +102,39 @@ install: #!/usr/bin/bash set -euo pipefail mkdir -p "$HOME/.local/bin" - - cat > "$HOME/.local/bin/claude" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ - -w "$(pwd)" \ - ghcr.io/binarypie-dev/ai-dev:latest \ - claude "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.claude"' \ + 'touch "$HOME/.claude.json"' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ + ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ + ' -w "$(pwd)" \' \ + ' ghcr.io/binarypie-dev/ai-dev:latest \' \ + ' claude "$@"' > "$HOME/.local/bin/claude" chmod +x "$HOME/.local/bin/claude" - - cat > "$HOME/.local/bin/gemini" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.gemini" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - ghcr.io/binarypie-dev/ai-dev:latest \ - gemini "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.gemini"' \ + 'env_flags=""' \ + 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ + ' env_flags="$env_flags -e $var"' \ + 'done' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' $env_flags \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ + ' -w "$(pwd)" \' \ + ' ghcr.io/binarypie-dev/ai-dev:latest \' \ + ' gemini "$@"' > "$HOME/.local/bin/gemini" chmod +x "$HOME/.local/bin/gemini" - echo "Installed wrapper scripts to ~/.local/bin/claude and ~/.local/bin/gemini" # Install wrapper scripts pointing to local image @@ -146,45 +142,39 @@ install-local: #!/usr/bin/bash set -euo pipefail mkdir -p "$HOME/.local/bin" - - cat > "$HOME/.local/bin/claude" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ - -w "$(pwd)" \ - localhost/ai-dev \ - claude "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.claude"' \ + 'touch "$HOME/.claude.json"' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ + ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ + ' -w "$(pwd)" \' \ + ' localhost/ai-dev \' \ + ' claude "$@"' > "$HOME/.local/bin/claude" chmod +x "$HOME/.local/bin/claude" - - cat > "$HOME/.local/bin/gemini" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.gemini" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - localhost/ai-dev \ - gemini "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.gemini"' \ + 'env_flags=""' \ + 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ + ' env_flags="$env_flags -e $var"' \ + 'done' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' $env_flags \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ + ' -w "$(pwd)" \' \ + ' localhost/ai-dev \' \ + ' gemini "$@"' > "$HOME/.local/bin/gemini" chmod +x "$HOME/.local/bin/gemini" - echo "Installed wrapper scripts (local image) to ~/.local/bin/claude and ~/.local/bin/gemini" # Remove wrapper scripts from ~/.local/bin @@ -226,7 +216,8 @@ test-build: build debug: #!/usr/bin/bash set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + mkdir -p "$HOME/.claude" "$HOME/.gemini" + touch "$HOME/.claude.json" env_flags="" for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do env_flags="$env_flags -e $var" @@ -236,7 +227,7 @@ debug: $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ -v "$HOME/.gemini:$HOME/.gemini:rw" \ -w "$(pwd)" \ {{local_image}} \ diff --git a/dot_files/ai-dev/ai-entrypoint.sh b/dot_files/ai-dev/ai-entrypoint.sh index bc438e2..06ca8fa 100644 --- a/dot_files/ai-dev/ai-entrypoint.sh +++ b/dot_files/ai-dev/ai-entrypoint.sh @@ -1,7 +1,25 @@ #!/usr/bin/bash set -e -export PATH="/home/linuxbrew/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/linuxbrew/.npm-global/bin:/home/linuxbrew/go/bin:/home/linuxbrew/.cargo/bin:$PATH" +# Fix root's home directory in /etc/passwd to match host $HOME +# This ensures os.homedir() in Node.js (used by claude/gemini) returns the correct path +if [ -n "$HOME" ] && [ "$HOME" != "/root" ]; then + sed -i "s|root:x:0:0:[^:]*:/root:|root:x:0:0:root:$HOME:|" /etc/passwd 2>/dev/null || true +fi + +# Symlink claude install paths to where the native installer expects them at runtime +# (installed under /home/linuxbrew at build time, but $HOME differs at runtime) +if [ -n "$HOME" ] && [ "$HOME" != "/home/linuxbrew" ]; then + mkdir -p "$HOME/.local/bin" "$HOME/.local/share" + ln -sf /home/linuxbrew/.local/bin/claude "$HOME/.local/bin/claude" 2>/dev/null || true + ln -sf /home/linuxbrew/.local/share/claude "$HOME/.local/share/claude" 2>/dev/null || true +fi + +# Ensure claude auth files are readable within the container +chmod -R a+rX "$HOME/.claude" 2>/dev/null || true +chmod a+rw "$HOME/.claude.json" 2>/dev/null || true + +export PATH="$HOME/.local/bin:/home/linuxbrew/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/linuxbrew/.npm-global/bin:/home/linuxbrew/go/bin:/home/linuxbrew/.cargo/bin:$PATH" # Debug mode: print environment and auth state if [ "$1" = "--ai-dev-debug" ]; then @@ -10,6 +28,9 @@ if [ "$1" = "--ai-dev-debug" ]; then echo "HOME=$HOME" echo "PATH=$PATH" echo "" + echo "=== /etc/passwd root entry ===" + grep "^root:" /etc/passwd + echo "" echo "=== TTY ===" ls -la /dev/pts/ 2>/dev/null || echo "no /dev/pts" echo "tty: $(tty 2>/dev/null || echo 'not a tty')" @@ -23,9 +44,6 @@ if [ "$1" = "--ai-dev-debug" ]; then echo "=== $HOME/.claude/ ===" ls -la "$HOME/.claude/" 2>/dev/null || echo "not found at $HOME/.claude/" echo "" - echo "=== $HOME/.config/claude/ ===" - ls -la "$HOME/.config/claude/" 2>/dev/null || echo "not found at $HOME/.config/claude/" - echo "" echo "=== which claude/gemini ===" which claude 2>/dev/null || echo "claude: not found" which gemini 2>/dev/null || echo "gemini: not found" diff --git a/system_files/shared/usr/share/ublue-os/just/62-ai.just b/system_files/shared/usr/share/ublue-os/just/62-ai.just index f02d880..9920af3 100644 --- a/system_files/shared/usr/share/ublue-os/just/62-ai.just +++ b/system_files/shared/usr/share/ublue-os/just/62-ai.just @@ -2,18 +2,19 @@ # Hypercube AI development environment commands ai_image := "ghcr.io/binarypie-dev/ai-dev:latest" -ai_podman_flags := "--rm -it --user 0:0 --security-opt label=disable" +ai_podman_flags := "--rm -it --init --user 0:0 --security-opt label=disable" # Run Claude Code in the current directory ai-claude *args: #!/usr/bin/bash set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" + mkdir -p "$HOME/.claude" + touch "$HOME/.claude.json" exec podman run {{ai_podman_flags}} \ -e HOME="$HOME" \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ -w "$(pwd)" \ {{ai_image}} \ claude {{args}} @@ -40,7 +41,8 @@ ai-gemini *args: ai-dev: #!/usr/bin/bash set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" "$HOME/.gemini" + mkdir -p "$HOME/.claude" "$HOME/.gemini" + touch "$HOME/.claude.json" env_flags="" for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do env_flags="$env_flags -e $var" @@ -50,7 +52,7 @@ ai-dev: $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ -v "$HOME/.gemini:$HOME/.gemini:rw" \ -w "$(pwd)" \ {{ai_image}} @@ -64,45 +66,39 @@ ai-setup: podman pull {{ai_image}} mkdir -p "$HOME/.local/bin" - - cat > "$HOME/.local/bin/claude" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.config/claude" - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.config/claude:$HOME/.config/claude:rw" \ - -w "$(pwd)" \ - ghcr.io/binarypie-dev/ai-dev:latest \ - claude "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.claude"' \ + 'touch "$HOME/.claude.json"' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ + ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ + ' -w "$(pwd)" \' \ + ' ghcr.io/binarypie-dev/ai-dev:latest \' \ + ' claude "$@"' > "$HOME/.local/bin/claude" chmod +x "$HOME/.local/bin/claude" - - cat > "$HOME/.local/bin/gemini" << 'WRAPPER' - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.gemini" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run --rm -it \ - --user 0:0 \ - --security-opt label=disable \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - ghcr.io/binarypie-dev/ai-dev:latest \ - gemini "$@" - WRAPPER + printf '%s\n' '#!/usr/bin/bash' \ + 'set -euo pipefail' \ + 'mkdir -p "$HOME/.gemini"' \ + 'env_flags=""' \ + 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ + ' env_flags="$env_flags -e $var"' \ + 'done' \ + 'exec podman run --rm -it --init \' \ + ' --user 0:0 \' \ + ' --security-opt label=disable \' \ + ' -e HOME="$HOME" \' \ + ' $env_flags \' \ + ' -v "$(pwd):$(pwd):rw" \' \ + ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ + ' -w "$(pwd)" \' \ + ' ghcr.io/binarypie-dev/ai-dev:latest \' \ + ' gemini "$@"' > "$HOME/.local/bin/gemini" chmod +x "$HOME/.local/bin/gemini" - echo "" echo "Setup complete! You can now run 'claude' and 'gemini' directly." From d53212efb6076899eef6a0c2a25bf152fbca447c Mon Sep 17 00:00:00 2001 From: binarypie Date: Fri, 23 Jan 2026 11:16:14 -0800 Subject: [PATCH 3/4] Centralize logic --- dot_files/ai-dev/Justfile | 159 +++--------------- dot_files/ai-dev/scripts/claude.sh | 18 ++ dot_files/ai-dev/scripts/enter.sh | 25 +++ dot_files/ai-dev/scripts/gemini.sh | 22 +++ .../shared/usr/share/ublue-os/just/62-ai.just | 92 +--------- 5 files changed, 93 insertions(+), 223 deletions(-) create mode 100755 dot_files/ai-dev/scripts/claude.sh create mode 100755 dot_files/ai-dev/scripts/enter.sh create mode 100755 dot_files/ai-dev/scripts/gemini.sh diff --git a/dot_files/ai-dev/Justfile b/dot_files/ai-dev/Justfile index b271116..bb0193a 100644 --- a/dot_files/ai-dev/Justfile +++ b/dot_files/ai-dev/Justfile @@ -1,12 +1,9 @@ # AI Development Environment (Sandboxed Podman Container) -# Uses entrypoint-based UID/GID mapping for correct permissions +# Uses scripts/ for shared logic between local dev and system install local_image := "localhost/ai-dev" remote_image := "ghcr.io/binarypie-dev/ai-dev:latest" -# Common podman run flags (UID 0 inside = host user in rootless podman) -podman_flags := "--rm -it --init --user 0:0 --security-opt label=disable" - # Default recipe - show help default: @just --list @@ -37,145 +34,44 @@ push: podman push {{remote_image}} # ============================================================================= -# Usage +# Usage (local image) # ============================================================================= # Run Claude Code in the current directory claude *args: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" - touch "$HOME/.claude.json" - exec podman run {{podman_flags}} \ - -e HOME="$HOME" \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ - -w "$(pwd)" \ - {{local_image}} \ - claude {{args}} + AI_DEV_IMAGE={{local_image}} ./scripts/claude.sh {{args}} # Run Gemini CLI in the current directory gemini *args: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.gemini" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run {{podman_flags}} \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - {{local_image}} \ - gemini {{args}} + AI_DEV_IMAGE={{local_image}} ./scripts/gemini.sh {{args}} # Enter the container interactively enter: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.gemini" - touch "$HOME/.claude.json" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run {{podman_flags}} \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - {{local_image}} + AI_DEV_IMAGE={{local_image}} ./scripts/enter.sh # ============================================================================= # Installation (wrapper scripts to ~/.local/bin) # ============================================================================= -# Install wrapper scripts for claude and gemini to ~/.local/bin +# Install wrapper scripts using remote image install: #!/usr/bin/bash set -euo pipefail mkdir -p "$HOME/.local/bin" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.claude"' \ - 'touch "$HOME/.claude.json"' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ - ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ - ' -w "$(pwd)" \' \ - ' ghcr.io/binarypie-dev/ai-dev:latest \' \ - ' claude "$@"' > "$HOME/.local/bin/claude" - chmod +x "$HOME/.local/bin/claude" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.gemini"' \ - 'env_flags=""' \ - 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ - ' env_flags="$env_flags -e $var"' \ - 'done' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' $env_flags \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ - ' -w "$(pwd)" \' \ - ' ghcr.io/binarypie-dev/ai-dev:latest \' \ - ' gemini "$@"' > "$HOME/.local/bin/gemini" - chmod +x "$HOME/.local/bin/gemini" - echo "Installed wrapper scripts to ~/.local/bin/claude and ~/.local/bin/gemini" - -# Install wrapper scripts pointing to local image + cp scripts/claude.sh "$HOME/.local/bin/claude" + cp scripts/gemini.sh "$HOME/.local/bin/gemini" + chmod +x "$HOME/.local/bin/claude" "$HOME/.local/bin/gemini" + echo "Installed ~/.local/bin/claude and ~/.local/bin/gemini (image: {{remote_image}})" + +# Install wrapper scripts using local image install-local: #!/usr/bin/bash set -euo pipefail mkdir -p "$HOME/.local/bin" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.claude"' \ - 'touch "$HOME/.claude.json"' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ - ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ - ' -w "$(pwd)" \' \ - ' localhost/ai-dev \' \ - ' claude "$@"' > "$HOME/.local/bin/claude" - chmod +x "$HOME/.local/bin/claude" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.gemini"' \ - 'env_flags=""' \ - 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ - ' env_flags="$env_flags -e $var"' \ - 'done' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' $env_flags \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ - ' -w "$(pwd)" \' \ - ' localhost/ai-dev \' \ - ' gemini "$@"' > "$HOME/.local/bin/gemini" - chmod +x "$HOME/.local/bin/gemini" - echo "Installed wrapper scripts (local image) to ~/.local/bin/claude and ~/.local/bin/gemini" + sed 's|ghcr.io/binarypie-dev/ai-dev:latest|localhost/ai-dev|' scripts/claude.sh > "$HOME/.local/bin/claude" + sed 's|ghcr.io/binarypie-dev/ai-dev:latest|localhost/ai-dev|' scripts/gemini.sh > "$HOME/.local/bin/gemini" + chmod +x "$HOME/.local/bin/claude" "$HOME/.local/bin/gemini" + echo "Installed ~/.local/bin/claude and ~/.local/bin/gemini (image: {{local_image}})" # Remove wrapper scripts from ~/.local/bin uninstall: @@ -207,28 +103,11 @@ clean: # Test the built image works correctly test-build: build @echo "Testing container..." - podman run {{podman_flags}} -e HOME="$HOME" {{local_image}} claude --version - podman run {{podman_flags}} -e HOME="$HOME" {{local_image}} gemini --version + AI_DEV_IMAGE={{local_image}} ./scripts/claude.sh --version + AI_DEV_IMAGE={{local_image}} ./scripts/gemini.sh --version @echo "" @echo "All tests passed!" # Debug: show container environment, paths, and auth state debug: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.gemini" - touch "$HOME/.claude.json" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - podman run {{podman_flags}} \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - {{local_image}} \ - --ai-dev-debug + AI_DEV_IMAGE={{local_image}} ./scripts/enter.sh --ai-dev-debug diff --git a/dot_files/ai-dev/scripts/claude.sh b/dot_files/ai-dev/scripts/claude.sh new file mode 100755 index 0000000..55fcfad --- /dev/null +++ b/dot_files/ai-dev/scripts/claude.sh @@ -0,0 +1,18 @@ +#!/usr/bin/bash +set -euo pipefail + +IMAGE="${AI_DEV_IMAGE:-ghcr.io/binarypie-dev/ai-dev:latest}" + +mkdir -p "$HOME/.claude" +touch "$HOME/.claude.json" + +exec podman run --rm -it --init \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ + -w "$(pwd)" \ + "$IMAGE" \ + claude "$@" diff --git a/dot_files/ai-dev/scripts/enter.sh b/dot_files/ai-dev/scripts/enter.sh new file mode 100755 index 0000000..ebbbcdc --- /dev/null +++ b/dot_files/ai-dev/scripts/enter.sh @@ -0,0 +1,25 @@ +#!/usr/bin/bash +set -euo pipefail + +IMAGE="${AI_DEV_IMAGE:-ghcr.io/binarypie-dev/ai-dev:latest}" + +mkdir -p "$HOME/.claude" "$HOME/.gemini" +touch "$HOME/.claude.json" + +env_flags="" +for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" +done + +exec podman run --rm -it --init \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.claude:$HOME/.claude:rw" \ + -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + "$IMAGE" \ + "$@" diff --git a/dot_files/ai-dev/scripts/gemini.sh b/dot_files/ai-dev/scripts/gemini.sh new file mode 100755 index 0000000..4e7d36c --- /dev/null +++ b/dot_files/ai-dev/scripts/gemini.sh @@ -0,0 +1,22 @@ +#!/usr/bin/bash +set -euo pipefail + +IMAGE="${AI_DEV_IMAGE:-ghcr.io/binarypie-dev/ai-dev:latest}" + +mkdir -p "$HOME/.gemini" + +env_flags="" +for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do + env_flags="$env_flags -e $var" +done + +exec podman run --rm -it --init \ + --user 0:0 \ + --security-opt label=disable \ + -e HOME="$HOME" \ + $env_flags \ + -v "$(pwd):$(pwd):rw" \ + -v "$HOME/.gemini:$HOME/.gemini:rw" \ + -w "$(pwd)" \ + "$IMAGE" \ + gemini "$@" diff --git a/system_files/shared/usr/share/ublue-os/just/62-ai.just b/system_files/shared/usr/share/ublue-os/just/62-ai.just index 9920af3..0d667a0 100644 --- a/system_files/shared/usr/share/ublue-os/just/62-ai.just +++ b/system_files/shared/usr/share/ublue-os/just/62-ai.just @@ -1,104 +1,30 @@ # vim: set ft=make : # Hypercube AI development environment commands -ai_image := "ghcr.io/binarypie-dev/ai-dev:latest" -ai_podman_flags := "--rm -it --init --user 0:0 --security-opt label=disable" +ai_scripts := "/usr/share/hypercube/config/ai-dev/scripts" # Run Claude Code in the current directory ai-claude *args: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" - touch "$HOME/.claude.json" - exec podman run {{ai_podman_flags}} \ - -e HOME="$HOME" \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ - -w "$(pwd)" \ - {{ai_image}} \ - claude {{args}} + {{ai_scripts}}/claude.sh {{args}} # Run Gemini CLI in the current directory ai-gemini *args: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.gemini" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run {{ai_podman_flags}} \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - {{ai_image}} \ - gemini {{args}} + {{ai_scripts}}/gemini.sh {{args}} # Enter the ai-dev container interactively ai-dev: - #!/usr/bin/bash - set -euo pipefail - mkdir -p "$HOME/.claude" "$HOME/.gemini" - touch "$HOME/.claude.json" - env_flags="" - for var in $(env | grep -E '^(GOOGLE_|GEMINI_|ANTHROPIC_)' | cut -d= -f1); do - env_flags="$env_flags -e $var" - done - exec podman run {{ai_podman_flags}} \ - -e HOME="$HOME" \ - $env_flags \ - -v "$(pwd):$(pwd):rw" \ - -v "$HOME/.claude:$HOME/.claude:rw" \ - -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ - -v "$HOME/.gemini:$HOME/.gemini:rw" \ - -w "$(pwd)" \ - {{ai_image}} + {{ai_scripts}}/enter.sh # Install claude and gemini wrapper scripts to ~/.local/bin ai-setup: #!/usr/bin/bash set -euo pipefail - echo "Pulling ai-dev image..." - podman pull {{ai_image}} - + podman pull ghcr.io/binarypie-dev/ai-dev:latest mkdir -p "$HOME/.local/bin" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.claude"' \ - 'touch "$HOME/.claude.json"' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.claude:$HOME/.claude:rw" \' \ - ' -v "$HOME/.claude.json:$HOME/.claude.json:rw" \' \ - ' -w "$(pwd)" \' \ - ' ghcr.io/binarypie-dev/ai-dev:latest \' \ - ' claude "$@"' > "$HOME/.local/bin/claude" - chmod +x "$HOME/.local/bin/claude" - printf '%s\n' '#!/usr/bin/bash' \ - 'set -euo pipefail' \ - 'mkdir -p "$HOME/.gemini"' \ - 'env_flags=""' \ - 'for var in $(env | grep -E '"'"'^(GOOGLE_|GEMINI_)'"'"' | cut -d= -f1); do' \ - ' env_flags="$env_flags -e $var"' \ - 'done' \ - 'exec podman run --rm -it --init \' \ - ' --user 0:0 \' \ - ' --security-opt label=disable \' \ - ' -e HOME="$HOME" \' \ - ' $env_flags \' \ - ' -v "$(pwd):$(pwd):rw" \' \ - ' -v "$HOME/.gemini:$HOME/.gemini:rw" \' \ - ' -w "$(pwd)" \' \ - ' ghcr.io/binarypie-dev/ai-dev:latest \' \ - ' gemini "$@"' > "$HOME/.local/bin/gemini" - chmod +x "$HOME/.local/bin/gemini" + cp {{ai_scripts}}/claude.sh "$HOME/.local/bin/claude" + cp {{ai_scripts}}/gemini.sh "$HOME/.local/bin/gemini" + chmod +x "$HOME/.local/bin/claude" "$HOME/.local/bin/gemini" echo "" echo "Setup complete! You can now run 'claude' and 'gemini' directly." @@ -107,5 +33,5 @@ ai-upgrade: #!/usr/bin/bash set -euo pipefail echo "Pulling latest ai-dev image..." - podman pull {{ai_image}} + podman pull ghcr.io/binarypie-dev/ai-dev:latest echo "Done! Latest image pulled." From f131f383a5b21529cc11853b670c0564596504a9 Mon Sep 17 00:00:00 2001 From: binarypie Date: Fri, 23 Jan 2026 11:46:17 -0800 Subject: [PATCH 4/4] add ai commands to justfile roll up --- system_files/shared/usr/share/ublue-os/justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/system_files/shared/usr/share/ublue-os/justfile b/system_files/shared/usr/share/ublue-os/justfile index c3797a6..52cd2d1 100644 --- a/system_files/shared/usr/share/ublue-os/justfile +++ b/system_files/shared/usr/share/ublue-os/justfile @@ -26,3 +26,4 @@ import "/usr/share/ublue-os/just/50-akmods.just" # ============================================================================= import? "/usr/share/ublue-os/just/60-hypercube.just" import? "/usr/share/ublue-os/just/61-nvim.just" +import? "/usr/share/ublue-os/just/62-ai.just"