mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 20:29:06 +00:00
feat: add Gemini CLI image with fixed user/group handling
- Add complete Gemini CLI container image for AI-powered development - Support Google Gemini models (1.5 Pro, Flash) with configurable settings - Include comprehensive plugin system for authentication and configuration - Fix user/group creation conflicts with existing base image users - Dynamic username handling for compatibility with node:20-slim base - Persistent configuration for .config/gemini and .cache/gemini - Test suite for Docker build, API key setup, and Cubbi integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
68
cubbi/images/gemini-cli/Dockerfile
Normal file
68
cubbi/images/gemini-cli/Dockerfile
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
FROM node:20-slim
|
||||||
|
|
||||||
|
LABEL maintainer="team@monadical.com"
|
||||||
|
LABEL description="Google Gemini CLI for Cubbi"
|
||||||
|
|
||||||
|
# Install system dependencies including gosu for user switching
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gosu \
|
||||||
|
passwd \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
bzip2 \
|
||||||
|
iputils-ping \
|
||||||
|
iproute2 \
|
||||||
|
libxcb1 \
|
||||||
|
libdbus-1-3 \
|
||||||
|
nano \
|
||||||
|
tmux \
|
||||||
|
git-core \
|
||||||
|
ripgrep \
|
||||||
|
openssh-client \
|
||||||
|
vim \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install uv (Python package manager) for cubbi_init.py compatibility
|
||||||
|
WORKDIR /tmp
|
||||||
|
RUN curl -fsSL https://astral.sh/uv/install.sh -o install.sh && \
|
||||||
|
sh install.sh && \
|
||||||
|
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
||||||
|
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
|
||||||
|
rm install.sh
|
||||||
|
|
||||||
|
# Install Gemini CLI globally
|
||||||
|
RUN npm install -g @google/gemini-cli
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
RUN gemini --version
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy initialization system
|
||||||
|
COPY cubbi_init.py /cubbi/cubbi_init.py
|
||||||
|
COPY gemini_cli_plugin.py /cubbi/gemini_cli_plugin.py
|
||||||
|
COPY cubbi_image.yaml /cubbi/cubbi_image.yaml
|
||||||
|
COPY init-status.sh /cubbi/init-status.sh
|
||||||
|
|
||||||
|
# Make scripts executable
|
||||||
|
RUN chmod +x /cubbi/cubbi_init.py /cubbi/init-status.sh
|
||||||
|
|
||||||
|
# Add init status check to bashrc
|
||||||
|
RUN echo '[ -x /cubbi/init-status.sh ] && /cubbi/init-status.sh' >> /etc/bash.bashrc
|
||||||
|
|
||||||
|
# Set up environment
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
|
||||||
|
# Pre-install the cubbi_init
|
||||||
|
RUN /cubbi/cubbi_init.py --help
|
||||||
|
|
||||||
|
# Set WORKDIR to /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENTRYPOINT ["/cubbi/cubbi_init.py"]
|
||||||
|
CMD ["tail", "-f", "/dev/null"]
|
||||||
339
cubbi/images/gemini-cli/README.md
Normal file
339
cubbi/images/gemini-cli/README.md
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
# Google Gemini CLI for Cubbi
|
||||||
|
|
||||||
|
This image provides Google Gemini CLI in a Cubbi container environment.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Google Gemini CLI is an AI-powered development tool that allows you to query and edit large codebases, generate applications from PDFs/sketches, automate operational tasks, and integrate with media generation tools using Google's Gemini models.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Advanced AI Models**: Access to Gemini 1.5 Pro, Flash, and other Google AI models
|
||||||
|
- **Codebase Analysis**: Query and edit large codebases intelligently
|
||||||
|
- **Multi-modal Support**: Work with text, images, PDFs, and sketches
|
||||||
|
- **Google Search Grounding**: Ground queries using Google Search for up-to-date information
|
||||||
|
- **Secure Authentication**: API key management through Cubbi's secure environment system
|
||||||
|
- **Persistent Configuration**: Settings and history preserved across container restarts
|
||||||
|
- **Project Integration**: Seamless integration with existing projects
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Set up API Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For Google AI (recommended)
|
||||||
|
uv run -m cubbi.cli config set services.google.api_key "your-gemini-api-key"
|
||||||
|
|
||||||
|
# Alternative using GEMINI_API_KEY
|
||||||
|
uv run -m cubbi.cli config set services.gemini.api_key "your-gemini-api-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Get your API key from [Google AI Studio](https://aistudio.google.com/apikey).
|
||||||
|
|
||||||
|
### 2. Run Gemini CLI Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start Gemini CLI container with your project
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli /path/to/your/project
|
||||||
|
|
||||||
|
# Or without a project
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use Gemini CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic usage
|
||||||
|
gemini
|
||||||
|
|
||||||
|
# Interactive mode with specific query
|
||||||
|
gemini
|
||||||
|
> Write me a Discord bot that answers questions using a FAQ.md file
|
||||||
|
|
||||||
|
# Analyze existing project
|
||||||
|
gemini
|
||||||
|
> Give me a summary of all changes that went in yesterday
|
||||||
|
|
||||||
|
# Generate from sketch/PDF
|
||||||
|
gemini
|
||||||
|
> Create a web app based on this wireframe.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Supported API Keys
|
||||||
|
|
||||||
|
- `GEMINI_API_KEY`: Google AI API key for Gemini models
|
||||||
|
- `GOOGLE_API_KEY`: Alternative Google API key (compatibility)
|
||||||
|
- `GOOGLE_APPLICATION_CREDENTIALS`: Path to Google Cloud service account JSON file
|
||||||
|
|
||||||
|
### Model Configuration
|
||||||
|
|
||||||
|
- `GEMINI_MODEL`: Default model (default: "gemini-1.5-pro")
|
||||||
|
- Available: "gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro"
|
||||||
|
- `GEMINI_TEMPERATURE`: Model temperature 0.0-2.0 (default: 0.7)
|
||||||
|
- `GEMINI_MAX_TOKENS`: Maximum tokens in response
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
- `GEMINI_SEARCH_ENABLED`: Enable Google Search grounding (true/false, default: false)
|
||||||
|
- `GEMINI_DEBUG`: Enable debug mode (true/false, default: false)
|
||||||
|
- `GCLOUD_PROJECT`: Google Cloud project ID
|
||||||
|
|
||||||
|
### Network Configuration
|
||||||
|
|
||||||
|
- `HTTP_PROXY`: HTTP proxy server URL
|
||||||
|
- `HTTPS_PROXY`: HTTPS proxy server URL
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic AI Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start Gemini CLI with your project
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli /path/to/project
|
||||||
|
|
||||||
|
# Inside the container:
|
||||||
|
gemini # Start interactive session
|
||||||
|
```
|
||||||
|
|
||||||
|
### Codebase Analysis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Analyze changes
|
||||||
|
gemini
|
||||||
|
> What are the main functions in src/main.py?
|
||||||
|
|
||||||
|
# Code generation
|
||||||
|
gemini
|
||||||
|
> Add error handling to the authentication module
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
gemini
|
||||||
|
> Generate README documentation for this project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-modal Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Work with images
|
||||||
|
gemini
|
||||||
|
> Analyze this architecture diagram and suggest improvements
|
||||||
|
|
||||||
|
# PDF processing
|
||||||
|
gemini
|
||||||
|
> Convert this API specification PDF to OpenAPI YAML
|
||||||
|
|
||||||
|
# Sketch to code
|
||||||
|
gemini
|
||||||
|
> Create a React component based on this UI mockup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Features
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With Google Search grounding
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_SEARCH_ENABLED="true" \
|
||||||
|
/path/to/project
|
||||||
|
|
||||||
|
# With specific model
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_MODEL="gemini-1.5-flash" \
|
||||||
|
--env GEMINI_TEMPERATURE="0.3" \
|
||||||
|
/path/to/project
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_DEBUG="true" \
|
||||||
|
/path/to/project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enterprise/Proxy Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With proxy
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env HTTPS_PROXY="https://proxy.company.com:8080" \
|
||||||
|
/path/to/project
|
||||||
|
|
||||||
|
# With Google Cloud authentication
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json" \
|
||||||
|
--env GCLOUD_PROJECT="your-project-id" \
|
||||||
|
/path/to/project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Persistent Configuration
|
||||||
|
|
||||||
|
The following directories are automatically persisted:
|
||||||
|
|
||||||
|
- `~/.config/gemini/`: Gemini CLI configuration files
|
||||||
|
- `~/.cache/gemini/`: Model cache and temporary files
|
||||||
|
|
||||||
|
Configuration files are maintained across container restarts, ensuring your preferences and session history are preserved.
|
||||||
|
|
||||||
|
## Model Recommendations
|
||||||
|
|
||||||
|
### Best Overall Performance
|
||||||
|
- **Gemini 1.5 Pro**: Excellent reasoning and code understanding
|
||||||
|
- **Gemini 1.5 Flash**: Faster responses, good for iterative development
|
||||||
|
|
||||||
|
### Cost-Effective Options
|
||||||
|
- **Gemini 1.5 Flash**: Lower cost, high speed
|
||||||
|
- **Gemini 1.0 Pro**: Basic model for simple tasks
|
||||||
|
|
||||||
|
### Specialized Use Cases
|
||||||
|
- **Code Analysis**: Gemini 1.5 Pro
|
||||||
|
- **Quick Iterations**: Gemini 1.5 Flash
|
||||||
|
- **Multi-modal Tasks**: Gemini 1.5 Pro (supports images, PDFs)
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
cubbi/images/gemini-cli/
|
||||||
|
├── Dockerfile # Container image definition
|
||||||
|
├── cubbi_image.yaml # Cubbi image configuration
|
||||||
|
├── gemini_plugin.py # Authentication and setup plugin
|
||||||
|
└── README.md # This documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication Flow
|
||||||
|
|
||||||
|
1. **API Key Setup**: API key configured via Cubbi configuration or environment variables
|
||||||
|
2. **Plugin Initialization**: `gemini_plugin.py` creates configuration files
|
||||||
|
3. **Environment File**: Creates `~/.config/gemini/.env` with API key
|
||||||
|
4. **Configuration**: Creates `~/.config/gemini/config.json` with settings
|
||||||
|
5. **Ready**: Gemini CLI is ready for use with configured authentication
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**No API Key Found**
|
||||||
|
```
|
||||||
|
ℹ️ No API key found - Gemini CLI will require authentication
|
||||||
|
```
|
||||||
|
**Solution**: Set API key in Cubbi configuration:
|
||||||
|
```bash
|
||||||
|
uv run -m cubbi.cli config set services.google.api_key "your-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Authentication Failed**
|
||||||
|
```
|
||||||
|
Error: Invalid API key or authentication failed
|
||||||
|
```
|
||||||
|
**Solution**: Verify your API key at [Google AI Studio](https://aistudio.google.com/apikey):
|
||||||
|
```bash
|
||||||
|
# Test your API key
|
||||||
|
curl -H "Content-Type: application/json" \
|
||||||
|
-d '{"contents":[{"parts":[{"text":"Hello"}]}]}' \
|
||||||
|
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Model Not Available**
|
||||||
|
```
|
||||||
|
Error: Model 'xyz' not found
|
||||||
|
```
|
||||||
|
**Solution**: Use supported models:
|
||||||
|
```bash
|
||||||
|
# List available models (inside container)
|
||||||
|
curl -H "Content-Type: application/json" \
|
||||||
|
"https://generativelanguage.googleapis.com/v1beta/models?key=YOUR_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rate Limit Exceeded**
|
||||||
|
```
|
||||||
|
Error: Quota exceeded
|
||||||
|
```
|
||||||
|
**Solution**: Google AI provides:
|
||||||
|
- 60 requests per minute
|
||||||
|
- 1,000 requests per day
|
||||||
|
- Consider upgrading to Google Cloud for higher limits
|
||||||
|
|
||||||
|
**Network/Proxy Issues**
|
||||||
|
```
|
||||||
|
Connection timeout or proxy errors
|
||||||
|
```
|
||||||
|
**Solution**: Configure proxy settings:
|
||||||
|
```bash
|
||||||
|
uv run -m cubbi.cli config set network.https_proxy "your-proxy-url"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable debug output
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_DEBUG="true"
|
||||||
|
|
||||||
|
# Check configuration
|
||||||
|
cat ~/.config/gemini/config.json
|
||||||
|
|
||||||
|
# Check environment
|
||||||
|
cat ~/.config/gemini/.env
|
||||||
|
|
||||||
|
# Test CLI directly
|
||||||
|
gemini --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **API Keys**: Stored securely with 0o600 permissions
|
||||||
|
- **Environment**: Isolated container environment
|
||||||
|
- **Configuration**: Secure file permissions for config files
|
||||||
|
- **Google Cloud**: Supports service account authentication for enterprise use
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Custom Model Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use specific model with custom settings
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_MODEL="gemini-1.5-flash" \
|
||||||
|
--env GEMINI_TEMPERATURE="0.2" \
|
||||||
|
--env GEMINI_MAX_TOKENS="8192"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Google Search Integration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable Google Search grounding for up-to-date information
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GEMINI_SEARCH_ENABLED="true"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Google Cloud Integration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use with Google Cloud service account
|
||||||
|
uv run -m cubbi.cli session create --image gemini-cli \
|
||||||
|
--env GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json" \
|
||||||
|
--env GCLOUD_PROJECT="your-project-id"
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Limits and Pricing
|
||||||
|
|
||||||
|
### Free Tier (Google AI)
|
||||||
|
- 60 requests per minute
|
||||||
|
- 1,000 requests per day
|
||||||
|
- Personal Google account required
|
||||||
|
|
||||||
|
### Paid Tier (Google Cloud)
|
||||||
|
- Higher rate limits
|
||||||
|
- Enterprise features
|
||||||
|
- Service account authentication
|
||||||
|
- Custom quotas available
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues related to:
|
||||||
|
- **Cubbi Integration**: Check Cubbi documentation or open an issue
|
||||||
|
- **Gemini CLI Functionality**: Visit [Gemini CLI documentation](https://github.com/google-gemini/gemini-cli)
|
||||||
|
- **Google AI Platform**: Visit [Google AI documentation](https://ai.google.dev/)
|
||||||
|
- **API Keys**: Visit [Google AI Studio](https://aistudio.google.com/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This image configuration is provided under the same license as the Cubbi project. Google Gemini CLI is licensed separately by Google.
|
||||||
80
cubbi/images/gemini-cli/cubbi_image.yaml
Normal file
80
cubbi/images/gemini-cli/cubbi_image.yaml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: gemini-cli
|
||||||
|
description: Google Gemini CLI environment for AI-powered development
|
||||||
|
version: 1.0.0
|
||||||
|
maintainer: team@monadical.com
|
||||||
|
image: monadical/cubbi-gemini-cli:latest
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# Google AI Configuration
|
||||||
|
- name: GEMINI_API_KEY
|
||||||
|
description: Google AI API key for Gemini models
|
||||||
|
required: false
|
||||||
|
sensitive: true
|
||||||
|
|
||||||
|
- name: GOOGLE_API_KEY
|
||||||
|
description: Alternative Google API key (compatibility)
|
||||||
|
required: false
|
||||||
|
sensitive: true
|
||||||
|
|
||||||
|
# Google Cloud Configuration
|
||||||
|
- name: GOOGLE_APPLICATION_CREDENTIALS
|
||||||
|
description: Path to Google Cloud service account JSON file
|
||||||
|
required: false
|
||||||
|
sensitive: true
|
||||||
|
|
||||||
|
- name: GCLOUD_PROJECT
|
||||||
|
description: Google Cloud project ID
|
||||||
|
required: false
|
||||||
|
|
||||||
|
# Model Configuration
|
||||||
|
- name: GEMINI_MODEL
|
||||||
|
description: Default Gemini model (e.g., gemini-1.5-pro, gemini-1.5-flash)
|
||||||
|
required: false
|
||||||
|
default: "gemini-1.5-pro"
|
||||||
|
|
||||||
|
- name: GEMINI_TEMPERATURE
|
||||||
|
description: Model temperature (0.0-2.0)
|
||||||
|
required: false
|
||||||
|
default: "0.7"
|
||||||
|
|
||||||
|
- name: GEMINI_MAX_TOKENS
|
||||||
|
description: Maximum tokens in response
|
||||||
|
required: false
|
||||||
|
|
||||||
|
# Search Configuration
|
||||||
|
- name: GEMINI_SEARCH_ENABLED
|
||||||
|
description: Enable Google Search grounding (true/false)
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
# Proxy Configuration
|
||||||
|
- name: HTTP_PROXY
|
||||||
|
description: HTTP proxy server URL
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- name: HTTPS_PROXY
|
||||||
|
description: HTTPS proxy server URL
|
||||||
|
required: false
|
||||||
|
|
||||||
|
# Debug Configuration
|
||||||
|
- name: GEMINI_DEBUG
|
||||||
|
description: Enable debug mode (true/false)
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
ports: []
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- mountPath: /app
|
||||||
|
description: Application directory
|
||||||
|
|
||||||
|
persistent_configs:
|
||||||
|
- source: "/home/cubbi/.config/gemini"
|
||||||
|
target: "/cubbi-config/gemini-settings"
|
||||||
|
type: "directory"
|
||||||
|
description: "Gemini CLI configuration and history"
|
||||||
|
|
||||||
|
- source: "/home/cubbi/.cache/gemini"
|
||||||
|
target: "/cubbi-config/gemini-cache"
|
||||||
|
type: "directory"
|
||||||
|
description: "Gemini CLI cache directory"
|
||||||
237
cubbi/images/gemini-cli/gemini_cli_plugin.py
Normal file
237
cubbi/images/gemini-cli/gemini_cli_plugin.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Gemini CLI Plugin for Cubbi
|
||||||
|
Handles authentication setup and configuration for Google Gemini CLI
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from cubbi_init import ToolPlugin
|
||||||
|
|
||||||
|
|
||||||
|
class GeminiCliPlugin(ToolPlugin):
|
||||||
|
"""Plugin for setting up Gemini CLI authentication and configuration"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tool_name(self) -> str:
|
||||||
|
return "gemini-cli"
|
||||||
|
|
||||||
|
def _get_user_ids(self) -> tuple[int, int]:
|
||||||
|
"""Get the cubbi user and group IDs from environment"""
|
||||||
|
user_id = int(os.environ.get("CUBBI_USER_ID", "1000"))
|
||||||
|
group_id = int(os.environ.get("CUBBI_GROUP_ID", "1000"))
|
||||||
|
return user_id, group_id
|
||||||
|
|
||||||
|
def _set_ownership(self, path: Path) -> None:
|
||||||
|
"""Set ownership of a path to the cubbi user"""
|
||||||
|
user_id, group_id = self._get_user_ids()
|
||||||
|
try:
|
||||||
|
os.chown(path, user_id, group_id)
|
||||||
|
except OSError as e:
|
||||||
|
self.status.log(f"Failed to set ownership for {path}: {e}", "WARNING")
|
||||||
|
|
||||||
|
def _get_gemini_config_dir(self) -> Path:
|
||||||
|
"""Get the Gemini configuration directory"""
|
||||||
|
return Path("/home/cubbi/.config/gemini")
|
||||||
|
|
||||||
|
def _get_gemini_cache_dir(self) -> Path:
|
||||||
|
"""Get the Gemini cache directory"""
|
||||||
|
return Path("/home/cubbi/.cache/gemini")
|
||||||
|
|
||||||
|
def _ensure_gemini_dirs(self) -> tuple[Path, Path]:
|
||||||
|
"""Ensure Gemini directories exist with correct ownership"""
|
||||||
|
config_dir = self._get_gemini_config_dir()
|
||||||
|
cache_dir = self._get_gemini_cache_dir()
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
for directory in [config_dir, cache_dir]:
|
||||||
|
try:
|
||||||
|
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||||
|
self._set_ownership(directory)
|
||||||
|
except OSError as e:
|
||||||
|
self.status.log(
|
||||||
|
f"Failed to create Gemini directory {directory}: {e}", "ERROR"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config_dir, cache_dir
|
||||||
|
|
||||||
|
def initialize(self) -> bool:
|
||||||
|
"""Initialize Gemini CLI configuration"""
|
||||||
|
self.status.log("Setting up Gemini CLI configuration...")
|
||||||
|
|
||||||
|
# Ensure Gemini directories exist
|
||||||
|
config_dir, cache_dir = self._ensure_gemini_dirs()
|
||||||
|
|
||||||
|
# Set up authentication and configuration
|
||||||
|
auth_configured = self._setup_authentication(config_dir)
|
||||||
|
config_created = self._create_configuration_file(config_dir)
|
||||||
|
|
||||||
|
if auth_configured or config_created:
|
||||||
|
self.status.log("✅ Gemini CLI configured successfully")
|
||||||
|
else:
|
||||||
|
self.status.log(
|
||||||
|
"ℹ️ No API key found - Gemini CLI will require authentication",
|
||||||
|
"INFO",
|
||||||
|
)
|
||||||
|
self.status.log(
|
||||||
|
" You can configure API keys using environment variables", "INFO"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Always return True to allow container to start
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _setup_authentication(self, config_dir: Path) -> bool:
|
||||||
|
"""Set up Gemini authentication"""
|
||||||
|
api_key = self._get_api_key()
|
||||||
|
|
||||||
|
if not api_key:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Create environment file for API key
|
||||||
|
env_file = config_dir / ".env"
|
||||||
|
try:
|
||||||
|
with open(env_file, "w") as f:
|
||||||
|
f.write(f"GEMINI_API_KEY={api_key}\n")
|
||||||
|
|
||||||
|
# Set ownership and secure file permissions
|
||||||
|
self._set_ownership(env_file)
|
||||||
|
os.chmod(env_file, stat.S_IRUSR | stat.S_IWUSR)
|
||||||
|
|
||||||
|
self.status.log(f"Created Gemini environment file at {env_file}")
|
||||||
|
self.status.log("Added Gemini API key")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.status.log(f"Failed to create environment file: {e}", "ERROR")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_api_key(self) -> str:
|
||||||
|
"""Get the Gemini API key from environment variables"""
|
||||||
|
# Check multiple possible environment variable names
|
||||||
|
for key_name in ["GEMINI_API_KEY", "GOOGLE_API_KEY"]:
|
||||||
|
api_key = os.environ.get(key_name)
|
||||||
|
if api_key:
|
||||||
|
return api_key
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _create_configuration_file(self, config_dir: Path) -> bool:
|
||||||
|
"""Create Gemini CLI configuration file"""
|
||||||
|
try:
|
||||||
|
config = self._build_configuration()
|
||||||
|
|
||||||
|
if not config:
|
||||||
|
return False
|
||||||
|
|
||||||
|
config_file = config_dir / "config.json"
|
||||||
|
with open(config_file, "w") as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
# Set ownership and permissions
|
||||||
|
self._set_ownership(config_file)
|
||||||
|
os.chmod(config_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||||
|
|
||||||
|
self.status.log(f"Created Gemini configuration at {config_file}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.status.log(f"Failed to create configuration file: {e}", "ERROR")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _build_configuration(self) -> Dict[str, Any]:
|
||||||
|
"""Build Gemini CLI configuration from environment variables"""
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
# Model configuration
|
||||||
|
model = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro")
|
||||||
|
if model:
|
||||||
|
config["defaultModel"] = model
|
||||||
|
self.status.log(f"Set default model to {model}")
|
||||||
|
|
||||||
|
# Temperature setting
|
||||||
|
temperature = os.environ.get("GEMINI_TEMPERATURE")
|
||||||
|
if temperature:
|
||||||
|
try:
|
||||||
|
temp_value = float(temperature)
|
||||||
|
if 0.0 <= temp_value <= 2.0:
|
||||||
|
config["temperature"] = temp_value
|
||||||
|
self.status.log(f"Set temperature to {temp_value}")
|
||||||
|
else:
|
||||||
|
self.status.log(
|
||||||
|
f"Invalid temperature value {temperature}, using default",
|
||||||
|
"WARNING",
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.status.log(
|
||||||
|
f"Invalid temperature format {temperature}, using default",
|
||||||
|
"WARNING",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Max tokens setting
|
||||||
|
max_tokens = os.environ.get("GEMINI_MAX_TOKENS")
|
||||||
|
if max_tokens:
|
||||||
|
try:
|
||||||
|
tokens_value = int(max_tokens)
|
||||||
|
if tokens_value > 0:
|
||||||
|
config["maxTokens"] = tokens_value
|
||||||
|
self.status.log(f"Set max tokens to {tokens_value}")
|
||||||
|
else:
|
||||||
|
self.status.log(
|
||||||
|
f"Invalid max tokens value {max_tokens}, using default",
|
||||||
|
"WARNING",
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.status.log(
|
||||||
|
f"Invalid max tokens format {max_tokens}, using default",
|
||||||
|
"WARNING",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Search configuration
|
||||||
|
search_enabled = os.environ.get("GEMINI_SEARCH_ENABLED", "false")
|
||||||
|
if search_enabled.lower() in ["true", "false"]:
|
||||||
|
config["searchEnabled"] = search_enabled.lower() == "true"
|
||||||
|
if config["searchEnabled"]:
|
||||||
|
self.status.log("Enabled Google Search grounding")
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
debug_mode = os.environ.get("GEMINI_DEBUG", "false")
|
||||||
|
if debug_mode.lower() in ["true", "false"]:
|
||||||
|
config["debug"] = debug_mode.lower() == "true"
|
||||||
|
if config["debug"]:
|
||||||
|
self.status.log("Enabled debug mode")
|
||||||
|
|
||||||
|
# Proxy settings
|
||||||
|
for proxy_var in ["HTTP_PROXY", "HTTPS_PROXY"]:
|
||||||
|
proxy_value = os.environ.get(proxy_var)
|
||||||
|
if proxy_value:
|
||||||
|
config[proxy_var.lower()] = proxy_value
|
||||||
|
self.status.log(f"Added proxy configuration: {proxy_var}")
|
||||||
|
|
||||||
|
# Google Cloud project
|
||||||
|
project = os.environ.get("GCLOUD_PROJECT")
|
||||||
|
if project:
|
||||||
|
config["project"] = project
|
||||||
|
self.status.log(f"Set Google Cloud project to {project}")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def setup_tool_configuration(self) -> bool:
|
||||||
|
"""Set up Gemini CLI configuration - called by base class"""
|
||||||
|
# Additional tool configuration can be added here if needed
|
||||||
|
return True
|
||||||
|
|
||||||
|
def integrate_mcp_servers(self, mcp_config: Dict[str, Any]) -> bool:
|
||||||
|
"""Integrate Gemini CLI with available MCP servers if applicable"""
|
||||||
|
if mcp_config["count"] == 0:
|
||||||
|
self.status.log("No MCP servers to integrate")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Gemini CLI doesn't have native MCP support,
|
||||||
|
# but we could potentially add custom integrations here
|
||||||
|
self.status.log(
|
||||||
|
f"Found {mcp_config['count']} MCP server(s) - no direct integration available"
|
||||||
|
)
|
||||||
|
return True
|
||||||
309
cubbi/images/gemini-cli/test_gemini.py
Normal file
309
cubbi/images/gemini-cli/test_gemini.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Comprehensive test script for Gemini CLI Cubbi image
|
||||||
|
Tests Docker image build, API key configuration, and Cubbi CLI integration
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(cmd, description="", check=True):
|
||||||
|
"""Run a shell command and return result"""
|
||||||
|
print(f"\n🔍 {description}")
|
||||||
|
print(f"Running: {cmd}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, shell=True, capture_output=True, text=True, check=check
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.stdout:
|
||||||
|
print("STDOUT:")
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
if result.stderr:
|
||||||
|
print("STDERR:")
|
||||||
|
print(result.stderr)
|
||||||
|
|
||||||
|
return result
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Command failed with exit code {e.returncode}")
|
||||||
|
if e.stdout:
|
||||||
|
print("STDOUT:")
|
||||||
|
print(e.stdout)
|
||||||
|
if e.stderr:
|
||||||
|
print("STDERR:")
|
||||||
|
print(e.stderr)
|
||||||
|
if check:
|
||||||
|
raise
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_build():
|
||||||
|
"""Test Docker image build"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Docker Image Build")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
"cd /home/bouthilx/projects/cubbi/cubbi/images/gemini-cli && docker build -t monadical/cubbi-gemini-cli:latest .",
|
||||||
|
"Building Gemini CLI Docker image",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
print("✅ Gemini CLI Docker image built successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Gemini CLI Docker image build failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_image_exists():
|
||||||
|
"""Test if the Gemini CLI Docker image exists"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Docker Image Existence")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
"docker images monadical/cubbi-gemini-cli:latest --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}'",
|
||||||
|
"Checking if Gemini CLI Docker image exists",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "monadical/cubbi-gemini-cli" in result.stdout:
|
||||||
|
print("✅ Gemini CLI Docker image exists")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Gemini CLI Docker image not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_gemini_version():
|
||||||
|
"""Test basic Gemini CLI functionality in container"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Gemini CLI Version")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
"docker run --rm monadical/cubbi-gemini-cli:latest bash -c 'gemini --version'",
|
||||||
|
"Testing Gemini CLI version command",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0 and (
|
||||||
|
"gemini" in result.stdout.lower() or "version" in result.stdout.lower()
|
||||||
|
):
|
||||||
|
print("✅ Gemini CLI version command works")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Gemini CLI version command failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_key_configuration():
|
||||||
|
"""Test API key configuration and environment setup"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing API Key Configuration")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Test with multiple API keys
|
||||||
|
test_keys = {
|
||||||
|
"GEMINI_API_KEY": "test-gemini-key",
|
||||||
|
"GOOGLE_API_KEY": "test-google-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
env_flags = " ".join([f'-e {key}="{value}"' for key, value in test_keys.items()])
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
f"docker run --rm {env_flags} monadical/cubbi-gemini-cli:latest bash -c 'cat ~/.config/gemini/.env 2>/dev/null || echo \"No .env file found\"'",
|
||||||
|
"Testing API key configuration in .env file",
|
||||||
|
)
|
||||||
|
|
||||||
|
success = True
|
||||||
|
if "test-gemini-key" in result.stdout:
|
||||||
|
print("✅ GEMINI_API_KEY configured correctly")
|
||||||
|
else:
|
||||||
|
print("❌ GEMINI_API_KEY not found in configuration")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def test_configuration_file():
|
||||||
|
"""Test Gemini CLI configuration file creation"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Configuration File")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
env_vars = "-e GEMINI_API_KEY='test-key' -e GEMINI_MODEL='gemini-1.5-pro' -e GEMINI_TEMPERATURE='0.5'"
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
f"docker run --rm {env_vars} monadical/cubbi-gemini-cli:latest bash -c 'cat ~/.config/gemini/config.json 2>/dev/null || echo \"No config file found\"'",
|
||||||
|
"Testing configuration file creation",
|
||||||
|
)
|
||||||
|
|
||||||
|
success = True
|
||||||
|
if "gemini-1.5-pro" in result.stdout:
|
||||||
|
print("✅ Default model configured correctly")
|
||||||
|
else:
|
||||||
|
print("❌ Default model not found in configuration")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
if "0.5" in result.stdout:
|
||||||
|
print("✅ Temperature configured correctly")
|
||||||
|
else:
|
||||||
|
print("❌ Temperature not found in configuration")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def test_cubbi_cli_integration():
|
||||||
|
"""Test Cubbi CLI integration"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Cubbi CLI Integration")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Test image listing
|
||||||
|
result = run_command(
|
||||||
|
"uv run -m cubbi.cli image list | grep gemini-cli",
|
||||||
|
"Testing Cubbi CLI can see Gemini CLI image",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "gemini-cli" in result.stdout:
|
||||||
|
print("✅ Cubbi CLI can list Gemini CLI image")
|
||||||
|
else:
|
||||||
|
print("❌ Cubbi CLI cannot see Gemini CLI image")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test session creation with test command
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
test_env = {
|
||||||
|
"GEMINI_API_KEY": "test-session-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
env_vars = " ".join([f"{k}={v}" for k, v in test_env.items()])
|
||||||
|
|
||||||
|
result = run_command(
|
||||||
|
f"{env_vars} uv run -m cubbi.cli session create --image gemini-cli {temp_dir} --no-shell --run \"gemini --version && echo 'Cubbi CLI test successful'\"",
|
||||||
|
"Testing Cubbi CLI session creation with Gemini CLI",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0 and "Cubbi CLI test successful" in result.stdout:
|
||||||
|
print("✅ Cubbi CLI session creation works")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Cubbi CLI session creation failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_persistent_configuration():
|
||||||
|
"""Test persistent configuration directories"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Persistent Configuration")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Test that persistent directories are created
|
||||||
|
result = run_command(
|
||||||
|
"docker run --rm -e GEMINI_API_KEY='test-key' monadical/cubbi-gemini-cli:latest bash -c 'ls -la /home/cubbi/.config/ && ls -la /home/cubbi/.cache/'",
|
||||||
|
"Testing persistent configuration directories",
|
||||||
|
)
|
||||||
|
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if "gemini" in result.stdout:
|
||||||
|
print("✅ ~/.config/gemini directory exists")
|
||||||
|
else:
|
||||||
|
print("❌ ~/.config/gemini directory not found")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
if "gemini" in result.stdout:
|
||||||
|
print("✅ ~/.cache/gemini directory exists")
|
||||||
|
else:
|
||||||
|
print("❌ ~/.cache/gemini directory not found")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_functionality():
|
||||||
|
"""Test the Gemini CLI plugin functionality"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 Testing Plugin Functionality")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Test plugin without API keys (should still work)
|
||||||
|
result = run_command(
|
||||||
|
"docker run --rm monadical/cubbi-gemini-cli:latest bash -c 'echo \"Plugin test without API keys\"'",
|
||||||
|
"Testing plugin functionality without API keys",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "No API key found - Gemini CLI will require authentication" in result.stdout:
|
||||||
|
print("✅ Plugin handles missing API keys gracefully")
|
||||||
|
else:
|
||||||
|
print("ℹ️ Plugin API key handling test - check output above")
|
||||||
|
|
||||||
|
# Test plugin with API keys
|
||||||
|
result = run_command(
|
||||||
|
"docker run --rm -e GEMINI_API_KEY='test-plugin-key' monadical/cubbi-gemini-cli:latest bash -c 'echo \"Plugin test with API keys\"'",
|
||||||
|
"Testing plugin functionality with API keys",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "Gemini CLI configured successfully" in result.stdout:
|
||||||
|
print("✅ Plugin configures environment successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Plugin environment configuration failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all tests"""
|
||||||
|
print("🚀 Starting Gemini CLI Cubbi Image Tests")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
("Docker Image Build", test_docker_build),
|
||||||
|
("Docker Image Exists", test_docker_image_exists),
|
||||||
|
("Gemini CLI Version", test_gemini_version),
|
||||||
|
("API Key Configuration", test_api_key_configuration),
|
||||||
|
("Configuration File", test_configuration_file),
|
||||||
|
("Persistent Configuration", test_persistent_configuration),
|
||||||
|
("Plugin Functionality", test_plugin_functionality),
|
||||||
|
("Cubbi CLI Integration", test_cubbi_cli_integration),
|
||||||
|
]
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for test_name, test_func in tests:
|
||||||
|
try:
|
||||||
|
results[test_name] = test_func()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Test '{test_name}' failed with exception: {e}")
|
||||||
|
results[test_name] = False
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("📊 TEST SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
total_tests = len(tests)
|
||||||
|
passed_tests = sum(1 for result in results.values() if result)
|
||||||
|
failed_tests = total_tests - passed_tests
|
||||||
|
|
||||||
|
for test_name, result in results.items():
|
||||||
|
status = "✅ PASS" if result else "❌ FAIL"
|
||||||
|
print(f"{status} {test_name}")
|
||||||
|
|
||||||
|
print(f"\nTotal: {total_tests} | Passed: {passed_tests} | Failed: {failed_tests}")
|
||||||
|
|
||||||
|
if failed_tests == 0:
|
||||||
|
print("\n🎉 All tests passed! Gemini CLI image is ready for use.")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print(f"\n⚠️ {failed_tests} test(s) failed. Please check the output above.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user