diff --git a/dot_files/ai-dev/Containerfile b/dot_files/ai-dev/Containerfile index 35a9092..706be0c 100644 --- a/dot_files/ai-dev/Containerfile +++ b/dot_files/ai-dev/Containerfile @@ -21,9 +21,18 @@ RUN curl -fsSL https://claude.ai/install.sh | bash RUN curl -fsSL https://antigravity.google/cli/install.sh | bash # ============================================================================= -# LAYER 3: Entrypoint script (sets PATH, execs command) +# LAYER 3: Ghostty terminfo (xterm-ghostty) # ============================================================================= +# Inherited from nvim-dev, but install explicitly so ai-dev still resolves +# TERM=xterm-ghostty if the base image ever changes. Prevents awkward escape +# sequences in the Claude Code / Antigravity TUIs. USER root +RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \ + | tic -x -o /etc/terminfo - + +# ============================================================================= +# LAYER 4: Entrypoint script (sets PATH, execs command) +# ============================================================================= COPY ai-entrypoint.sh /usr/local/bin/ai-entrypoint.sh RUN chmod +x /usr/local/bin/ai-entrypoint.sh diff --git a/dot_files/ai-dev/ai-entrypoint.sh b/dot_files/ai-dev/ai-entrypoint.sh index 8834b9c..5016b78 100644 --- a/dot_files/ai-dev/ai-entrypoint.sh +++ b/dot_files/ai-dev/ai-entrypoint.sh @@ -7,15 +7,21 @@ 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 native install paths to where the installers expect 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 - ln -sf /home/linuxbrew/.local/bin/agy "$HOME/.local/bin/agy" 2>/dev/null || true +# Make sure TERM resolves inside the container. The host usually runs Ghostty +# (TERM=xterm-ghostty); that terminfo is baked into the image. If an unknown or +# empty TERM is propagated, fall back to a sane default so programs don't emit +# raw/awkward escape sequences. +if [ -z "${TERM:-}" ] || ! infocmp "$TERM" >/dev/null 2>&1; then + export TERM=xterm-256color fi +# Note: claude/agy are installed under /home/linuxbrew/.local/bin at build time. +# They are resolved at runtime via PATH (set below), NOT via symlinks into $HOME. +# Symlinking into $HOME was unsafe: when the wrappers mount the current directory +# (-v "$(pwd):$(pwd):rw") and the tool is run from the host home directory, those +# symlinks were written straight through to the host, clobbering the real +# ~/.local/bin/claude and ~/.local/bin/agy wrapper scripts. + # 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 diff --git a/dot_files/ai-dev/scripts/agy.sh b/dot_files/ai-dev/scripts/agy.sh index 5ad0e8c..794a071 100755 --- a/dot_files/ai-dev/scripts/agy.sh +++ b/dot_files/ai-dev/scripts/agy.sh @@ -14,6 +14,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.gemini:$HOME/.gemini:rw" \ diff --git a/dot_files/ai-dev/scripts/claude.sh b/dot_files/ai-dev/scripts/claude.sh index 55fcfad..72cccf9 100755 --- a/dot_files/ai-dev/scripts/claude.sh +++ b/dot_files/ai-dev/scripts/claude.sh @@ -10,6 +10,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ diff --git a/dot_files/ai-dev/scripts/enter.sh b/dot_files/ai-dev/scripts/enter.sh index a87c6d5..2afb709 100755 --- a/dot_files/ai-dev/scripts/enter.sh +++ b/dot_files/ai-dev/scripts/enter.sh @@ -15,6 +15,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ diff --git a/dot_files/nvim/Containerfile b/dot_files/nvim/Containerfile index 1bd0c9a..f74884d 100644 --- a/dot_files/nvim/Containerfile +++ b/dot_files/nvim/Containerfile @@ -190,8 +190,10 @@ USER root WORKDIR / # ============================================================================= -# LAYER 11: Terminal compatibility +# LAYER 11: Terminal compatibility (xterm-ghostty terminfo) # ============================================================================= +# Install Ghostty's terminfo so TERM=xterm-ghostty resolves inside the +# container and TUIs (nvim, lazygit, ...) don't emit awkward escape sequences. RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \ | tic -x -o /etc/terminfo - @@ -202,6 +204,27 @@ RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/te # all tool directories are accessible RUN chmod -R 777 /home/linuxbrew +# ============================================================================= +# LAYER 13: TERM fallback safety net (xterm-ghostty) +# ============================================================================= +# distrobox shares the host TERM (usually xterm-ghostty) and the terminfo is +# installed above. As a safety net, if an unknown or empty TERM ever reaches an +# interactive shell, fall back to xterm-256color so programs don't emit raw +# escape sequences. Covers POSIX shells and fish (distrobox uses the host shell). +RUN mkdir -p /etc/profile.d /etc/fish/conf.d \ + && printf '%s\n' \ + '# Fall back to a known-good TERM if the current one has no terminfo entry.' \ + 'if [ -n "${TERM:-}" ] && ! infocmp "$TERM" >/dev/null 2>&1; then' \ + ' export TERM=xterm-256color' \ + 'fi' \ + > /etc/profile.d/10-term-fallback.sh \ + && printf '%s\n' \ + '# Fall back to a known-good TERM if the current one has no terminfo entry.' \ + 'if test -n "$TERM"; and not infocmp "$TERM" >/dev/null 2>&1' \ + ' set -gx TERM xterm-256color' \ + 'end' \ + > /etc/fish/conf.d/10-term-fallback.fish + # Labels for GitHub Container Registry LABEL org.opencontainers.image.source="https://github.com/binarypie/hypercube" LABEL org.opencontainers.image.description="Neovim development environment with LSPs, formatters, and tools"