From 233f56ddf2a0a1b1718f0c74887dd23636b6278f Mon Sep 17 00:00:00 2001 From: hessius Date: Mon, 9 Feb 2026 13:14:57 +0100 Subject: [PATCH 1/8] chore: remove deprecated files for v2.0.0 Phase 6 cleanup for #136: Removed installation scripts (replaced by scripts/install.sh): - local-install.sh (1400+ lines) - web_install.sh - update.sh (900+ lines) - uninstall.sh - rebuild-watcher.sh - docker-up.sh - check-updates-on-start.sh Removed obsolete directories: - gemini-client/ (Gemini CLI now installed in unified image) - macos-installer/ (no longer needed with Docker-based install) Removed outdated documentation: - UPDATE_GUIDE.md - PHASE2_COMPLETION_REPORT.md, PHASE3_PLAN.md, PHASE4_PLAN.md - REFACTORING_SUMMARY.md, TEST_FIXES.md - DEPENDENCY_UPDATE_SUMMARY.md, FINAL_SUMMARY.md Removed old compose files: - docker-compose.old Part of #136 --- DEPENDENCY_UPDATE_SUMMARY.md | 136 -- FINAL_SUMMARY.md | 53 - PHASE2_COMPLETION_REPORT.md | 205 --- PHASE3_PLAN.md | 119 -- PHASE4_PLAN.md | 125 -- REFACTORING_SUMMARY.md | 193 --- TEST_FIXES.md | 278 ---- UPDATE_GUIDE.md | 361 ----- check-updates-on-start.sh | 41 - docker-compose.old | 34 - docker-up.sh | 76 - gemini-client/Dockerfile | 21 - gemini-client/settings.json | 21 - gemini-client/settings.json.old | 19 - local-install.sh | 1632 --------------------- macos-installer/BUGFIX_SUMMARY.md | 252 ---- macos-installer/ENHANCEMENTS_SUMMARY.md | 224 --- macos-installer/FULLY_GUI_SUMMARY.md | 262 ---- macos-installer/FUTURE_ENHANCEMENTS.md | 285 ---- macos-installer/IMPLEMENTATION_SUMMARY.md | 317 ---- macos-installer/QUICKSTART.md | 218 --- macos-installer/README.md | 398 ----- macos-installer/build-macos-app.sh | 194 --- macos-installer/build-uninstaller-app.sh | 188 --- macos-installer/install-wrapper.sh | 749 ---------- macos-installer/uninstall-wrapper.sh | 430 ------ rebuild-watcher.sh | 528 ------- uninstall.sh | 607 -------- update.sh | 1099 -------------- web_install.sh | 376 ----- 30 files changed, 9441 deletions(-) delete mode 100644 DEPENDENCY_UPDATE_SUMMARY.md delete mode 100644 FINAL_SUMMARY.md delete mode 100644 PHASE2_COMPLETION_REPORT.md delete mode 100644 PHASE3_PLAN.md delete mode 100644 PHASE4_PLAN.md delete mode 100644 REFACTORING_SUMMARY.md delete mode 100644 TEST_FIXES.md delete mode 100644 UPDATE_GUIDE.md delete mode 100755 check-updates-on-start.sh delete mode 100644 docker-compose.old delete mode 100755 docker-up.sh delete mode 100644 gemini-client/Dockerfile delete mode 100644 gemini-client/settings.json delete mode 100644 gemini-client/settings.json.old delete mode 100755 local-install.sh delete mode 100644 macos-installer/BUGFIX_SUMMARY.md delete mode 100644 macos-installer/ENHANCEMENTS_SUMMARY.md delete mode 100644 macos-installer/FULLY_GUI_SUMMARY.md delete mode 100644 macos-installer/FUTURE_ENHANCEMENTS.md delete mode 100644 macos-installer/IMPLEMENTATION_SUMMARY.md delete mode 100644 macos-installer/QUICKSTART.md delete mode 100644 macos-installer/README.md delete mode 100755 macos-installer/build-macos-app.sh delete mode 100755 macos-installer/build-uninstaller-app.sh delete mode 100755 macos-installer/install-wrapper.sh delete mode 100755 macos-installer/uninstall-wrapper.sh delete mode 100755 rebuild-watcher.sh delete mode 100755 uninstall.sh delete mode 100755 update.sh delete mode 100755 web_install.sh diff --git a/DEPENDENCY_UPDATE_SUMMARY.md b/DEPENDENCY_UPDATE_SUMMARY.md deleted file mode 100644 index 9a9a1fd..0000000 --- a/DEPENDENCY_UPDATE_SUMMARY.md +++ /dev/null @@ -1,136 +0,0 @@ -# Dependency Update Summary - -## Overview - -This document summarizes the dependency updates applied to align the refactored codebase with the latest stable versions from the main branch. - -## Updated Dependencies - -### Production Dependencies (requirements.txt) - -| Package | Old Version | New Version | Change Type | -|---------|------------|-------------|-------------| -| `uvicorn` | 0.27.0 | **0.40.0** | Minor version bump | -| `google-generativeai` | 0.3.2 | **0.8.6** | Significant minor update | -| `python-multipart` | 0.0.18 | **0.0.22** | Patch version | - -### Test Dependencies (requirements-test.txt) - -| Package | Old Version | New Version | Change Type | -|---------|------------|-------------|-------------| -| `pytest-asyncio` | 0.23.3 | **1.3.0** | **Major version bump** | -| `pytest-cov` | 4.1.0 | **7.0.0** | **Major version bump** | - -## Impact Analysis - -### 1. uvicorn (0.27.0 → 0.40.0) - -**Changes:** -- Performance improvements in ASGI server -- Better WebSocket handling -- Improved error messages - -**Code Impact:** ✅ None - Backward compatible -**Action Required:** None - -### 2. google-generativeai (0.3.2 → 0.8.6) - -**Changes:** -- Enhanced type hints -- New model capabilities (multimodal improvements) -- Better error handling -- Streaming improvements - -**Code Impact:** ✅ None - Our usage is compatible -**API Usage Verified:** -- `genai.configure(api_key=...)` - Compatible -- `genai.GenerativeModel('gemini-2.0-flash')` - Compatible -- No breaking changes in our use cases - -**Action Required:** None - -### 3. python-multipart (0.0.18 → 0.0.22) - -**Changes:** -- Security fixes -- Bug fixes in multipart parsing -- Performance improvements - -**Code Impact:** ✅ None - Internal library used by FastAPI -**Action Required:** None - -### 4. pytest-asyncio (0.23.3 → 1.3.0) ⚠️ Major Version - -**Breaking Changes:** -- Requires explicit `asyncio_mode` configuration -- Changes to fixture loop scope behavior - -**Code Impact:** ✅ Addressed -**Changes Made:** -- Added `asyncio_mode = auto` to `pytest.ini` -- Added `asyncio_default_fixture_loop_scope = function` for proper test isolation -- All async tests will now run with automatic mode detection - -**Action Required:** ✅ Complete - Configuration updated - -### 5. pytest-cov (4.1.0 → 7.0.0) ⚠️ Major Version - -**Changes:** -- Updated to support latest coverage.py -- Better integration with pytest 7+ -- Improved branch coverage reporting - -**Code Impact:** ✅ None - Compatible with our existing test setup -**Action Required:** None - -## Configuration Changes - -### pytest.ini - -**Added:** -```ini -asyncio_mode = auto -asyncio_default_fixture_loop_scope = function -``` - -**Purpose:** -- `asyncio_mode = auto`: Automatically detects async tests and runs them appropriately -- `asyncio_default_fixture_loop_scope = function`: Ensures each test gets a fresh event loop for proper isolation - -## Verification - -All updates have been verified: - -1. ✅ **Syntax Check**: All Python files compile without errors -2. ✅ **API Compatibility**: Google Gemini API usage verified compatible -3. ✅ **Test Configuration**: pytest.ini updated for pytest-asyncio 1.x -4. ✅ **No Breaking Changes**: All code patterns remain valid - -## Migration Notes - -If you're pulling these changes: - -1. **No code changes required** - All updates are backward compatible -2. **Run `pip install -r requirements.txt -r requirements-test.txt`** to update dependencies -3. **Tests will continue to work** with the updated pytest configuration - -## Security & Stability - -All updated versions are: -- ✅ Stable releases (not pre-release or beta) -- ✅ Widely adopted in production -- ✅ Include security fixes from previous versions -- ✅ Compatible with Python 3.11+ - -## References - -- [uvicorn Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) -- [google-generativeai Releases](https://github.com/google/generative-ai-python/releases) -- [pytest-asyncio Changelog](https://github.com/pytest-dev/pytest-asyncio/blob/main/CHANGELOG.rst) -- [pytest-cov Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - ---- - -**Updated:** 2026-02-08 -**Status:** ✅ Complete and Verified -**Breaking Changes:** None (with configuration updates applied) diff --git a/FINAL_SUMMARY.md b/FINAL_SUMMARY.md deleted file mode 100644 index 22af6dc..0000000 --- a/FINAL_SUMMARY.md +++ /dev/null @@ -1,53 +0,0 @@ -# Comprehensive Refactoring - Final Summary - -## Executive Summary - -Successfully completed a comprehensive 4-phase refactoring of the MeticAI codebase, transforming a monolithic 7,230-line application into a well-organized, modular architecture while eliminating 300+ lines of duplicate code and updating all dependencies. - -## Metrics - -### Code Reduction -- **Main.py**: 7,230 → 1,325 lines (**82% reduction**) -- **Shell Scripts**: ~300 lines of duplication eliminated -- **Total Modularized**: 9,744 lines across 15 focused modules - -### Files Created -- **15 new modules**: 6 routes, 7 services, 2 utilities -- **1 config module**: Unified configuration management -- **1 shared library**: Shell script common functions -- **6 documentation files**: Comprehensive guides - -### Tests Updated -- **352 test functions** modernized for modular architecture -- **198 @patch decorators** updated to new module paths -- **72 service imports** added - -## Phase-by-Phase Summary - -### Phase 0: Naming Standardization ✅ -**Result**: Renamed coffee-relay → meticai-server (100+ occurrences) - -### Phase 1: Quick Wins & Shell Consolidation ✅ -**Result**: ~300 lines removed, quality improvements - -### Phase 2: Modularization ✅ -**Result**: 82% reduction, 15 focused modules created - -### Phase 3: Test Modernization ✅ -**Result**: 352 tests modernized, ready for coverage enhancement - -### Phase 4: Documentation & Configuration ✅ -**Result**: Unified config, comprehensive Docker documentation - -## Security - -- ✅ **0 security alerts** from CodeQL scan -- ✅ Documented Docker volume security risks -- ✅ All security patches in updated dependencies - -## Status - -✅ **ALL PHASES COMPLETE** -✅ Zero security alerts -✅ 100% backward compatible -✅ Ready for merge diff --git a/PHASE2_COMPLETION_REPORT.md b/PHASE2_COMPLETION_REPORT.md deleted file mode 100644 index 635bcad..0000000 --- a/PHASE2_COMPLETION_REPORT.md +++ /dev/null @@ -1,205 +0,0 @@ -# Phase 2 Modularization - Completion Report - -## Executive Summary - -Phase 2 of the MeticAI refactoring is **complete**. The monolithic 7,230-line `main.py` file has been successfully decomposed into 15 focused, testable modules. - -## Transformation Metrics - -### Before (Monolithic) -``` -main.py: 7,230 lines -- 121 functions -- 46 API endpoints -- Difficult to navigate -- High coupling -- Hard to test -``` - -### After (Modular) -``` -Total: 9,744 lines across 15 files - -main.py: 1,325 lines (82% reduction!) -- App setup only -- Route registration -- Lifecycle management - -API Routes: 6,284 lines (6 files, 46 endpoints) -Services: 2,017 lines (7 files, 48+ functions) -Utils: 117 lines (2 files) -``` - -## Module Breakdown - -### API Routes (6 modules, 46 endpoints) - -| Module | Lines | Endpoints | Responsibility | -|--------|-------|-----------|----------------| -| `api/routes/coffee.py` | 435 | 2 | Coffee bag analysis, profile creation | -| `api/routes/system.py` | 976 | 10 | Status, updates, logs, settings, version | -| `api/routes/history.py` | 290 | 6 | Profile history CRUD, migration | -| `api/routes/shots.py` | 818 | 7 | Shot data retrieval and analysis | -| `api/routes/profiles.py` | 2,953 | 11 | Profile images, import/export | -| `api/routes/scheduling.py` | 812 | 10 | Machine control, shot scheduling | - -### Services (7 modules, 48+ functions) - -| Module | Lines | Functions | Responsibility | -|--------|-------|-----------|----------------| -| `services/cache_service.py` | 238 | 14 | LLM, shot, and image caching | -| `services/settings_service.py` | 59 | 4 | Settings management | -| `services/history_service.py` | 155 | 7 | Profile history operations | -| `services/gemini_service.py` | 140 | 4 | AI/Gemini integration | -| `services/meticulous_service.py` | 131 | 4 | Espresso machine API | -| `services/analysis_service.py` | 1,147 | 15 | Shot analysis logic | -| `services/scheduling_state.py` | 145 | - | Scheduling state management | - -### Utilities (2 modules) - -| Module | Lines | Functions | Responsibility | -|--------|-------|-----------|----------------| -| `utils/file_utils.py` | 74 | 2 | Atomic writes, JSON conversion | -| `utils/sanitization.py` | 43 | 2 | Input sanitization | - -## Key Achievements - -### ✅ Code Organization -- **Clear separation of concerns** - Each module has a single responsibility -- **Domain-driven structure** - Modules organized by feature area -- **Easy navigation** - Developers can find code by functionality - -### ✅ Improved Testability -- **Isolated testing** - Each service/route can be tested independently -- **Mock-friendly** - Clear boundaries for dependency injection -- **Reduced setup** - Tests can import only what they need - -### ✅ Maintainability -- **Smaller files** - Largest route module is 2,953 lines vs 7,230 original -- **Focused modules** - Each file does one thing well -- **Better readability** - Less scrolling, clearer structure - -### ✅ Developer Experience -- **Faster IDE performance** - Smaller files load faster -- **Better IntelliSense** - Clearer import paths -- **Easier onboarding** - New developers can understand modules quickly - -### ✅ Zero Breaking Changes -- **100% backward compatible** - All API contracts preserved -- **Same endpoints** - No URL changes -- **Same behavior** - All functionality maintained - -## Technical Details - -### Import Strategy -All route modules use APIRouter: -```python -from fastapi import APIRouter -router = APIRouter() - -@router.post("/analyze_coffee") -async def analyze_coffee(...): - ... -``` - -### Main.py Registration -```python -from api.routes import coffee, system, history, shots, profiles, scheduling - -app.include_router(coffee.router) -app.include_router(system.router) -app.include_router(history.router) -app.include_router(shots.router) -app.include_router(profiles.router) -app.include_router(scheduling.router) -``` - -### Service Imports -Services are imported where needed: -```python -from services.cache_service import get_cached_llm_analysis -from services.gemini_service import get_vision_model -from services.analysis_service import perform_local_shot_analysis -``` - -## Code Quality Improvements - -During extraction, the following issues were fixed: -- ✅ Deprecated API usage (`app.on_event` → `lifespan`) -- ✅ Magic numbers replaced with named constants -- ✅ Missing function definitions added -- ✅ All docstrings preserved - -## Validation - -All code has been validated: -- ✅ **Syntax check** - All 15 files compile without errors -- ✅ **Import check** - All imports resolve correctly -- ✅ **Structure check** - Router registration verified - -## Next Steps (Phase 3) - -With the modularization complete, the foundation is in place for: - -1. **Test Coverage Enhancement** - - Update test imports to use new modules - - Add unit tests for each service module - - Add integration tests for route modules - - Target: 70%+ coverage (up from current 25%) - -2. **API Documentation** - - Add OpenAPI examples to route modules - - Document request/response schemas - - Add usage examples - -3. **Configuration Management** - - Create unified config module with pydantic-settings - - Consolidate scattered configuration - -4. **Performance Optimization** - - Profile the modular code - - Optimize imports - - Add caching where beneficial - -## Files Changed - -### Created (17 new files): -- `api/__init__.py` -- `api/routes/__init__.py` -- `api/routes/coffee.py` -- `api/routes/system.py` -- `api/routes/history.py` -- `api/routes/shots.py` -- `api/routes/profiles.py` -- `api/routes/scheduling.py` -- `services/__init__.py` -- `services/cache_service.py` -- `services/settings_service.py` -- `services/history_service.py` -- `services/gemini_service.py` -- `services/meticulous_service.py` -- `services/analysis_service.py` -- `utils/__init__.py` -- `utils/file_utils.py` -- `utils/sanitization.py` - -### Modified: -- `main.py` (7,230 → 1,325 lines) - -## Conclusion - -Phase 2 modularization has successfully transformed the MeticAI codebase from a monolithic architecture to a well-organized, modular system. The code is now: -- **More maintainable** - Smaller, focused modules -- **More testable** - Clear boundaries for testing -- **More scalable** - Easy to add new features -- **More approachable** - Easier for new developers - -The refactoring maintains 100% backward compatibility while dramatically improving code quality and developer experience. - ---- - -**Phase 2 Status**: ✅ **COMPLETE** -**Lines Refactored**: 9,744 lines across 15 modules -**Reduction**: 82% (main.py: 7,230 → 1,325 lines) -**Quality**: All files compile, all functionality preserved -**Next**: Phase 3 - Test Coverage Enhancement diff --git a/PHASE3_PLAN.md b/PHASE3_PLAN.md deleted file mode 100644 index 02cf7c5..0000000 --- a/PHASE3_PLAN.md +++ /dev/null @@ -1,119 +0,0 @@ -# Phase 3: Test Coverage Enhancement - Implementation Plan - -## Overview - -Phase 3 focuses on improving test coverage from the current ~25% to 70%+ on critical paths. With the modularized codebase from Phase 2, we can now test components in isolation. - -## Current State - -- **Test File**: `test_main.py` (7,362 lines, 352 test functions) -- **Current Coverage**: ~25% (369/1504 statements) -- **Target Coverage**: 70%+ on critical paths - -## Phase 3 Goals - -### 1. Update Test Imports ✅ PRIORITY 1 -- Update imports in `test_main.py` to use new modular structure -- Change from testing monolithic `main.py` to testing individual modules -- Ensure all existing tests still pass - -### 2. Add Service Module Tests 📝 PRIORITY 2 -Create dedicated test files for each service: -- `tests/test_cache_service.py` - Cache management (14 functions) -- `tests/test_settings_service.py` - Settings operations (4 functions) -- `tests/test_history_service.py` - History management (7 functions) -- `tests/test_gemini_service.py` - AI integration (4 functions) -- `tests/test_meticulous_service.py` - Machine API (4 functions) -- `tests/test_analysis_service.py` - Shot analysis (15 functions) - -### 3. Add Route Module Tests 📝 PRIORITY 3 -Create dedicated test files for each route module: -- `tests/test_routes_coffee.py` - Coffee analysis endpoints (2 endpoints) -- `tests/test_routes_system.py` - System endpoints (10 endpoints) -- `tests/test_routes_history.py` - History CRUD (6 endpoints) -- `tests/test_routes_shots.py` - Shot endpoints (7 endpoints) -- `tests/test_routes_profiles.py` - Profile management (11 endpoints) -- `tests/test_routes_scheduling.py` - Scheduling (10 endpoints) - -### 4. Add Utility Tests 📝 PRIORITY 4 -- `tests/test_file_utils.py` - File operations (2 functions) -- `tests/test_sanitization.py` - Input sanitization (2 functions) - -## Critical Path Focus Areas - -Based on the issue, focus testing on: -1. ✅ Shot analysis functions (`_analyze_stage_execution`, `_perform_local_shot_analysis`) -2. ✅ Profile image generation and processing -3. ✅ Scheduling system (recurring schedules, shot execution) -4. ✅ Machine API interaction layer -5. ✅ Cache management functions - -## Testing Strategy - -### Unit Tests -- Test each service/utility function in isolation -- Mock external dependencies (API calls, file I/O, database) -- Use pytest fixtures for common setup - -### Integration Tests -- Test route modules with full FastAPI TestClient -- Test end-to-end workflows -- Validate API contracts - -### Test Organization -- Follow existing patterns from `test_main.py` -- Group related tests into test classes -- Use descriptive test names -- Add docstrings explaining test purpose - -## Success Criteria - -- ✅ All existing tests pass after import updates -- ✅ 70%+ coverage on service modules -- ✅ 70%+ coverage on critical path functions -- ✅ All new tests pass in CI/CD -- ✅ No decrease in overall code quality - -## Implementation Steps - -### Step 1: Update Existing Test Imports -1. Analyze current test structure -2. Update imports to use new modules -3. Run tests and fix any failures -4. Commit working tests - -### Step 2: Add Service Tests -1. Start with cache_service (most functions) -2. Add analysis_service tests (critical path) -3. Add remaining service tests -4. Verify coverage improvements - -### Step 3: Add Route Tests -1. Start with coffee routes (entry point) -2. Add system routes (health checks) -3. Add remaining route tests -4. Ensure integration tests work - -### Step 4: Add Utility Tests -1. Test file_utils -2. Test sanitization -3. Verify edge cases - -### Step 5: Verify and Document -1. Run full test suite -2. Generate coverage report -3. Document coverage improvements -4. Update test documentation - -## Notes - -- Maintain existing test patterns and conventions -- Use pytest-asyncio for async tests -- Mock external services (Gemini API, Meticulous machine) -- Keep tests fast and independent -- Follow TDD principles for new features - ---- - -**Status**: Planning Complete - Ready to Begin Implementation -**Next**: Update test imports in `test_main.py` diff --git a/PHASE4_PLAN.md b/PHASE4_PLAN.md deleted file mode 100644 index 14d9bb9..0000000 --- a/PHASE4_PLAN.md +++ /dev/null @@ -1,125 +0,0 @@ -# Phase 4: Documentation & Configuration - Implementation Plan - -## Overview - -Phase 4 focuses on improving API documentation and creating a unified configuration management system. - -## Goals - -### 1. Add Comprehensive API Documentation -- Add detailed docstrings to all API endpoints -- Include parameter descriptions -- Document response schemas -- Add error response documentation -- Consider OpenAPI examples - -### 2. Create Unified Configuration Module -- Consolidate scattered configuration -- Use pydantic-settings for type-safe configuration -- Environment variables centralized -- Constants defined in one place -- Easy to test and mock - -### 3. Document Docker Compose Volume Mounts -- Document purpose of each volume mount -- Identify which are essential vs optional -- Security review of mounts (.git, docker.sock) -- Consider reducing where possible - -## Implementation Steps - -### Step 1: Create Unified Config Module - -Create `meticai-server/config.py` (simple class-based approach for no new dependencies): -```python -class Config: - # Environment-based configuration - GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") - METICULOUS_IP = os.environ.get("METICULOUS_IP") - - # Application constants - UPDATE_CHECK_INTERVAL = 7200 # 2 hours - MAX_UPLOAD_SIZE = 10 * 1024 * 1024 # 10 MB - - # Cache settings - LLM_CACHE_TTL_SECONDS = 259200 # 3 days - SHOT_CACHE_STALE_SECONDS = 3600 # 1 hour -``` - -Note: Using simple class instead of pydantic-settings to avoid adding new dependencies. - -### Step 2: Update Code to Use Config - -- Replace hardcoded constants with config.settings -- Update services to accept config as dependency -- Make testing easier with configurable values - -### Step 3: Add API Documentation - -For each route module, enhance docstrings: -```python -@router.post("/analyze_coffee") -async def analyze_coffee( - request: Request, - file: UploadFile = File(...) -): - """Analyze a coffee bag image using Google Gemini Vision AI. - - Args: - request: FastAPI request object - file: Coffee bag image (PNG, JPEG, or WEBP, max 10MB) - - Returns: - JSONResponse containing: - - analysis (str): Coffee identification and characteristics - - status (str): "success" - - timestamp (str): ISO 8601 timestamp - - Raises: - HTTPException(400): If file is too large or invalid format - HTTPException(500): If Gemini API fails - - Example: - POST /analyze_coffee - Content-Type: multipart/form-data - - Response: - { - "analysis": "Ethiopian Yirgacheffe, Light Roast, Floral Notes", - "status": "success", - "timestamp": "2026-02-08T14:47:00Z" - } - """ -``` - -### Step 4: Document Docker Volumes - -Create `DOCKER_VOLUMES.md` documenting each mount: - -| Mount | Purpose | Essential | Security Notes | -|-------|---------|-----------|----------------| -| `/var/run/docker.sock` | Container management | Yes | Risk: Full Docker access | -| `./.git` | Version tracking | No | Consider removing | -| `./data` | Persistent data | Yes | Safe | -| `./logs` | Log files | Yes | Safe | - -### Step 5: Verify and Test - -- Ensure config module works -- Test with environment variables -- Verify all imports work -- Update tests to use config - -## Success Criteria - -- ✅ All API endpoints have comprehensive docstrings -- ✅ Unified config module created -- ✅ All constants moved to config -- ✅ Docker volumes documented -- ✅ Tests pass with config changes -- ✅ Code review passes - ---- - -**Status**: Planning Complete - Ready to Implement -**Next**: Create config.py module diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md deleted file mode 100644 index cbfe078..0000000 --- a/REFACTORING_SUMMARY.md +++ /dev/null @@ -1,193 +0,0 @@ -# MeticAI Refactoring Summary - -This document summarizes the comprehensive refactoring work completed to improve code maintainability, reduce technical debt, and establish a foundation for future scalability. - -## Completed Phases - -### Phase 0: Repository Renaming ✅ - -**Objective**: Rename "coffee-relay" to "meticai-server" for naming consistency with MeticAI branding. - -**Changes Made**: -- Renamed directory: `coffee-relay/` → `meticai-server/` -- Updated Docker service name in `docker-compose.yml` -- Updated container name: `coffee-relay` → `meticai-server` -- Updated all Python logger names and log file references -- Updated all shell scripts (7 files) -- Updated all documentation (7 MD files + copilot instructions) -- Updated CI/CD workflows (`.github/workflows/tests.yml`) -- Updated macOS installer files - -**Impact**: -- 100+ occurrences of "coffee-relay" replaced with "meticai-server" -- Log files now named: `meticai-server.log` and `meticai-server-errors.log` -- Logger name updated to: `meticai-server` -- All syntax validated (Python, YAML, Bash) - -### Phase 1: Quick Wins ✅ - -**Objective**: Fix immediate code quality issues and eliminate code duplication. - -#### 1.1 Python Code Improvements - -**Type Hints Fix**: -- Fixed `_resolve_variable()` function at line 3852 -- Changed `tuple[any, str | None]` → `tuple[Any, str | None]` -- Added proper import: `from typing import Optional, Any` - -**Logging Fix**: -- Removed duplicate logger initialization (lines 36-38) -- Now uses single logger from `logging_config.setup_logging()` -- Eliminated potential configuration conflicts - -**Dependency Pinning**: -Updated `meticai-server/requirements.txt`: -``` -pyMeticulous>=0.1.0 → pyMeticulous==0.1.0 -zstandard>=0.22.0 → zstandard==0.22.0 -httpx>=0.26.0 → httpx==0.26.0 -``` - -#### 1.2 Shell Script Consolidation - -**Created Shared Library**: `scripts/lib/common.sh` - -**Functions Provided**: -- **Colors**: GREEN, BLUE, YELLOW, RED, NC -- **Logging**: log_info(), log_success(), log_error(), log_warning(), show_progress() -- **Docker Utilities**: check_docker(), check_docker_compose(), get_compose_command(), stop_containers() -- **Git Utilities**: checkout_latest_release() -- **System Utilities**: run_privileged(), check_prerequisites() -- **Validation**: validate_ip(), validate_not_empty() -- **File Operations**: check_file_exists(), ensure_directory() - -**Scripts Updated** (7 total): -1. `docker-up.sh` - 13 lines removed -2. `check-updates-on-start.sh` - 7 lines removed -3. `rebuild-watcher.sh` - 4 lines removed, updated logging -4. `uninstall.sh` - 80+ echo statements replaced -5. `web_install.sh` - 30+ logging calls updated -6. `update.sh` - 87 logging calls replaced -7. `local-install.sh` - 100+ logging calls updated - -**Impact**: -- **~300 lines of duplicate code eliminated** -- Centralized error handling and logging -- Consistent color coding across all scripts -- Easier maintenance and debugging -- All scripts pass syntax validation - -## Code Quality Metrics - -### Before Refactoring -- Type hints: 1 instance of unsafe `any` type -- Duplicate code: ~300 lines across shell scripts -- Naming: Inconsistent (coffee-relay vs MeticAI) -- Logging: Duplicate initialization -- Dependencies: 3 unpinned versions - -### After Refactoring -- Type hints: ✅ All use proper `Any` type -- Duplicate code: ✅ Eliminated via shared library -- Naming: ✅ Consistent (meticai-server) -- Logging: ✅ Single initialization point -- Dependencies: ✅ All versions pinned - -## Testing & Validation - -All changes have been validated: -- ✅ Python syntax check: All `.py` files compile successfully -- ✅ Bash syntax check: All `.sh` files pass `bash -n` validation -- ✅ YAML syntax check: `docker-compose.yml` passes `docker compose config` -- ✅ Shared library test: Functions work correctly with color output - -## Future Work (Not Yet Implemented) - -### Phase 2: Modularization -- Extract `meticai-server/main.py` (7,234 lines, 121 functions) into modules: - - `api/routes/` - Route handlers by domain - - `services/` - Business logic services - - `utils/` - Utility functions - - `models/` - Pydantic schemas - -### Phase 3: Test Coverage Enhancement -- Current coverage: ~25% (369/1504 statements) -- Target: 70%+ coverage on critical paths -- Focus areas: - - Shot analysis functions - - Profile image generation - - Scheduling system - - Machine API layer - - Cache management - -### Phase 4: Documentation & Configuration -- Add comprehensive API docstrings -- Create unified config module with pydantic-settings -- Document Docker volume mounts -- Update API.md with new structure - -## Migration Notes for Developers - -### If You're Working on Old Branches - -After this refactoring, you'll need to update: - -1. **Directory References**: - - Old: `coffee-relay/` - - New: `meticai-server/` - -2. **Container Names**: - - Old: `coffee-relay` - - New: `meticai-server` - -3. **Log File Names**: - - Old: `coffee-relay.log`, `coffee-relay-errors.log` - - New: `meticai-server.log`, `meticai-server-errors.log` - -4. **Shell Scripts**: - - All scripts now source `scripts/lib/common.sh` - - Use library functions instead of duplicate code - - Follow established patterns in updated scripts - -### For New Development - -1. **Python Code**: - - Use `from typing import Any` for generic types - - Never use lowercase `any` - - Pin all dependency versions - - Use logger from `logging_config.setup_logging()` - -2. **Shell Scripts**: - - Always source the common library - - Use `log_*()` functions for output - - Use library utilities instead of reimplementing - - Test with `bash -n scriptname.sh` - -3. **Naming**: - - Server component: `meticai-server` - - Web component: `meticai-web` - - Logger name: `meticai-server` - - Project name: MeticAI (capital A and I) - -## Contributors - -This refactoring was completed to address technical debt identified in issue #[issue_number]. - -Special thanks to the comprehensive analysis that identified: -- Monolithic code structure -- Code duplication patterns -- Inconsistent naming -- Missing type hints - -## Related Documentation - -- See [TECHNICAL.md](TECHNICAL.md) for technical architecture -- See [README.md](README.md) for usage and installation -- See [TEST_COVERAGE.md](TEST_COVERAGE.md) for testing guidelines -- See `.github/copilot-instructions.md` for AI assistant context - ---- - -*Last Updated: 2026-02-08* -*Refactoring Phases Completed: 0, 1* -*Refactoring Phases Remaining: 2, 3, 4* diff --git a/TEST_FIXES.md b/TEST_FIXES.md deleted file mode 100644 index 48bdd6f..0000000 --- a/TEST_FIXES.md +++ /dev/null @@ -1,278 +0,0 @@ -# Test Fixes Documentation - -## Overview -This document explains the test failures that occurred after recent bugfixes and how they were resolved without rolling back the improvements. - ---- - -## Latest Fix (January 2026) - Post-Caching Implementation - -### Problem Statement -After adding server-side LLM analysis caching, **19 Python tests were failing**: -- **5 LLM analysis tests**: Permission denied errors on `/app/data` -- **6 Barista persona tests**: TypeError when accessing NoneType -- **8 analyze_and_profile tests**: Status returning 'error' instead of 'success' - -### Root Causes - -#### 1. Hardcoded Data Paths -**Issue**: All data files used hardcoded `/app/data` paths which don't exist in test environment. - -**Root Cause**: Paths were defined as constants at module level: -```python -SETTINGS_FILE = Path("/app/data/settings.json") -HISTORY_FILE = Path("/app/data/profile_history.json") -LLM_CACHE_FILE = Path("/app/data/llm_analysis_cache.json") -SHOT_CACHE_FILE = Path("/app/data/shot_cache.json") -IMAGE_CACHE_DIR = Path("/app/data/image_cache") -``` - -When tests ran, these paths couldn't be created due to permission issues, causing errors. - -**Fix**: Made paths configurable via environment variables: -```python -# In main.py -TEST_MODE = os.environ.get("TEST_MODE") == "true" -if TEST_MODE: - DATA_DIR = Path(tempfile.gettempdir()) / "meticai_test_data" - DATA_DIR.mkdir(parents=True, exist_ok=True) -else: - DATA_DIR = Path(os.environ.get("DATA_DIR", "/app/data")) - -# Then use DATA_DIR for all paths -SETTINGS_FILE = DATA_DIR / "settings.json" -HISTORY_FILE = DATA_DIR / "profile_history.json" -# etc. -``` - -Created `conftest.py` to set environment variables before main.py imports: -```python -# conftest.py runs at import time before tests -os.environ["TEST_MODE"] = "true" -test_data_dir = tempfile.mkdtemp(prefix="meticai_test_") -os.environ["DATA_DIR"] = test_data_dir -``` - -#### 2. Missing Null Safety in Image Prompt Generation -**Issue**: `TypeError: 'NoneType' object is not subscriptable` in 6 barista tests. - -**Root Cause**: In `generate_profile_image()`, code accessed dictionary keys without checking if result was None: -```python -prompt_result = build_image_prompt_with_metadata(...) -full_prompt = prompt_result["prompt"] # TypeError if None! -``` - -**Fix**: Added null checking and safe access: -```python -prompt_result = build_image_prompt_with_metadata(...) - -if not prompt_result or not isinstance(prompt_result, dict): - raise HTTPException( - status_code=500, - detail="Failed to build image generation prompt" - ) - -full_prompt = prompt_result.get("prompt", "") -prompt_metadata = prompt_result.get("metadata", {}) -``` - -#### 3. LLM Analysis Cache Persistence -**Issue**: Tests expecting specific LLM behavior were hitting cached results from previous tests. - -**Root Cause**: The new LLM caching feature (with 3-day TTL) was persisting data across tests because all tests shared the same temp directory. - -**Fix**: Added `force_refresh=true` parameter to all LLM analysis tests: -```python -response = client.post("/api/shots/analyze-llm", data={ - "profile_name": "Test", - "shot_date": "2024-01-15", - "shot_filename": "shot.json", - "force_refresh": "true" # Bypass cache in tests -}) -``` - -#### 4. History File Mock Issue -**Issue**: Test `test_load_history_with_missing_file` expected empty list but got data from previous tests. - -**Root Cause**: Test was trying to mock `Path` class but the module-level constant `HISTORY_FILE` was already evaluated. Also, the file actually existed from previous tests. - -**Fix**: Changed test to mock `open()` to raise `FileNotFoundError`: -```python -@patch('main._ensure_history_file') -@patch('builtins.open') -def test_load_history_with_missing_file(self, mock_open_func, mock_ensure): - mock_open_func.side_effect = FileNotFoundError("File not found") - history = _load_history() - assert history == [] -``` - -### Changes Made - -#### File: `meticai-server/main.py` -- **Lines 39-47**: Added `DATA_DIR` configuration with test mode support -- **Lines 1016, 1235, 1266, 1337, 1413**: Updated all hardcoded paths to use `DATA_DIR` -- **Lines 2411-2426**: Added null safety checks for `prompt_result` in `generate_profile_image()` -- **Impact**: Enables tests to run with temporary directories, prevents NoneType errors - -#### File: `meticai-server/conftest.py` (new file) -- **Purpose**: Set up test environment variables before main.py is imported -- **Lines 10-16**: Configure TEST_MODE and DATA_DIR for all tests -- **Impact**: All tests automatically use temporary directories - -#### File: `meticai-server/test_main.py` -- **Lines 3497, 3520, 3563, 3611, 3666, 3682**: Added `force_refresh="true"` to 6 LLM analysis tests -- **Lines 1843-1856**: Fixed `test_load_history_with_missing_file` to mock `open()` instead of `Path` -- **Impact**: Tests properly bypass cache and handle file operations - -### Test Results - -#### Before Fixes -- **19 failures**, 160 passed -- Permission errors on `/app/data` -- NoneType subscript errors -- Unexpected cache hits - -#### After Fixes -- **0 failures**, 192 passed ✅ (test_main.py) -- **0 failures**, 19 passed ✅ (test_logging.py) -- **Total: 211 tests passing** - -### Benefits Added - -This fix maintains all recent improvements while adding: - -1. **Test Environment Isolation**: Tests use temporary directories, preventing conflicts -2. **Production Flexibility**: `DATA_DIR` can be configured via environment variable -3. **Robust Error Handling**: Null safety prevents crashes on edge cases -4. **Cache Control**: Tests can bypass cache when needed for deterministic behavior - -### Validation - -All fixes validated by: -1. Running full Python test suite (192 + 19 = 211 tests) -2. Verifying bash scripts have valid syntax -3. Confirming no production behavior changed -4. Ensuring proper separation of test and production environments - ---- - -## Previous Fix (Earlier) - Status and Update Endpoints -After merging several PRs with bugfixes and improvements (#32, #37, #39, #42, #45, #47), CI tests started failing: -- **10 Python tests failing** (all in `/status` and `/api/trigger-update` endpoints) -- **1 Bash test failing** (macOS dock shortcut prompt text) - -## Root Causes - -### 1. Bash Test Failure (test #32) -**Issue**: Test expected text "Would you like to add a MeticAI shortcut" but actual script had different text. - -**Root Cause**: PR #37 added macOS dock shortcut functionality with the prompt "Would you like to add MeticAI to your Dock?" but the test wasn't updated to match. - -**Fix**: Updated test to match actual script text. -```bash -# Before: -run grep -q "Would you like to add a MeticAI shortcut" "$SCRIPT_PATH" - -# After: -run grep -q "Would you like to add MeticAI to your Dock?" "$SCRIPT_PATH" -``` - -### 2. Python Tests - `/status` Endpoint (2 failures) -**Issue**: Tests expected subprocess calls but got different behavior. - -**Root Cause**: PR #32 changed `/status` endpoint implementation from running `update.sh --check-only` via subprocess to reading directly from `.versions.json` file. Tests were still mocking subprocess calls instead of file I/O. - -**Original Implementation** (what tests expected): -```python -result = subprocess.run(["bash", "update.sh", "--check-only"], ...) -# Parse stdout to determine update availability -``` - -**New Implementation** (what code actually does): -```python -version_file_path = Path("/app/.versions.json") -if version_file_path.exists(): - with open(version_file_path, 'r') as f: - version_data = json.load(f) -``` - -**Fix**: Updated tests to mock `Path.exists()` and `open()` instead of `subprocess.run()`. - -### 3. Python Tests - `/api/trigger-update` Endpoint (8 failures) -**Issue**: All tests returned 500 errors instead of expected 200/test-specific responses. - -**Root Cause**: Endpoint checks if `/app/update.sh` exists before running it: -```python -script_path = Path("/app/update.sh") -if not script_path.exists(): - raise HTTPException(status_code=500, detail={...}) -``` - -Tests were mocking `subprocess.run()` but not `Path.exists()`, so the check failed and raised a 500 error before subprocess was ever called. - -**Fix**: Added `@patch('main.Path')` to all affected tests and configured the mock to return a Path object where `.exists()` returns `True`. - -```python -# Before: -@patch('main.subprocess.run') -def test_trigger_update_success(self, mock_subprocess, client): - ... - -# After: -@patch('main.subprocess.run') -@patch('main.Path') -def test_trigger_update_success(self, mock_subprocess, mock_path_class, client): - mock_script_path = Mock() - mock_script_path.exists.return_value = True - mock_path_class.return_value = mock_script_path - ... -``` - -## Changes Made - -### File: `tests/test_local_install.bats` -- **Line 177**: Updated expected prompt text to match actual implementation -- **Impact**: Minimal - single line change - -### File: `meticai-server/test_main.py` -- **Line 13**: Added `mock_open` import for file mocking -- **TestStatusEndpoint (6 tests)**: - - Removed `subprocess.run` mocks - - Added `Path` and `open()` mocks - - Configured mocks to simulate file existence and JSON content -- **TestTriggerUpdateEndpoint (8 tests)**: - - Added `Path` mocks to all tests - - Configured `Path.exists()` to return `True` - - Maintained all other test logic unchanged -- **Impact**: Updated test mocking to match new implementation, no test coverage lost - -## Test Results - -### Before Fixes -- Python: 10 failures, 39 passing -- Bash: 1 failure, 50 passing - -### After Fixes -- Python: 0 failures, 49 passing ✅ -- Bash: 0 failures, 51 passing ✅ - -## Benefits Maintained - -All improvements from recent bugfixes remain intact: - -1. **Update System** (PR #32): Automatic repository switching, version tracking, API-accessible status -2. **macOS Dock Shortcut** (PR #37): Easy access to web app for macOS users -3. **Trigger Update API** (PR #39): Programmatic update triggering for web interfaces -4. **Installation Improvements** (PRs #42, #45, #47): Early .env detection, automatic IP discovery, network scanning, QR codes - -## Validation - -All fixes were validated by: -1. Running full Python test suite locally (49 tests) -2. Running full Bash test suite locally (51 tests) -3. Verifying no functionality was removed or degraded -4. Confirming all tests use proper mocking patterns - -## Conclusion - -The test failures were caused by tests not being updated to match improved implementations. By updating the test mocking to match the new behavior, all tests now pass while preserving all the improvements from recent bugfixes. diff --git a/UPDATE_GUIDE.md b/UPDATE_GUIDE.md deleted file mode 100644 index eccff71..0000000 --- a/UPDATE_GUIDE.md +++ /dev/null @@ -1,361 +0,0 @@ -# MeticAI Update System - -This document provides a quick reference for the MeticAI update system. - -## Quick Start - -```bash -# Check for updates -./update.sh --check-only - -# Apply updates -./update.sh - -# Auto-update (non-interactive, great for automation) -./update.sh --auto -``` - -## What Gets Updated? - -The update system manages three components: - -1. **MeticAI Main Repository** - The core application (version-based) -2. **Meticulous MCP** - The machine control protocol server (commit-based) -3. **MeticAI Web Interface** - The web-based user interface (version-based) - -## Features - -### 1. Update Checking -- Uses semantic versioning for MeticAI and MeticAI-web repos -- Compares local `VERSION` file with remote on GitHub -- Displays clear version numbers (e.g., v1.0.0 → v1.1.0) -- Non-invasive - doesn't modify anything in check-only mode - -### 2. Dependency Management -- Automatically clones missing dependencies -- Updates existing dependencies to latest version -- Preserves your `.env` configuration -- **Automatic repository switching** based on central configuration - -### 3. Automatic Repository Switching -- Repository URLs controlled centrally via `.update-config.json` -- Maintainers can switch all users to new repositories without manual intervention -- Automatic detection and switching during updates -- Backups created before switching -- Works seamlessly in both interactive and auto mode - -### 4. Container Management -- Optionally rebuilds Docker containers after updates -- Restarts services with updated code -- Handles docker compose versions automatically - -### 4. API Integration -- `/status` endpoint returns update availability -- JSON response with detailed version information -- CORS-enabled for web app integration -- Lightweight and fast - -### 5. Startup Notifications -- Automatically checks for updates on container start -- Displays notification banner if updates available -- Non-blocking background check -- Doesn't slow down startup - -## Command Reference - -### Update Script Options - -| Option | Description | -|--------|-------------| -| `--check-only` | Check for updates without applying them | -| `--auto` | Run non-interactively (auto-accept all prompts) | -| `--switch-mcp-repo` | Check and apply central repository configuration | -| `--help` | Show help message | - -**Note on Repository Switching:** The `--switch-mcp-repo` option is now primarily for manual checking. Repository switching happens **automatically** based on the central `.update-config.json` file when you run normal updates. - -### Examples - -**Check what updates are available:** -```bash -./update.sh --check-only -``` - -**Apply updates interactively:** -```bash -./update.sh -# You'll be asked: -# - Whether to apply updates -# - Whether to rebuild containers -``` - -**Apply updates automatically (CI/CD):** -```bash -./update.sh --auto -# No prompts, applies all updates and rebuilds -# Automatically switches repositories based on central config -``` - -**Check repository configuration:** -```bash -./update.sh --switch-mcp-repo -# Checks and applies central repository configuration -# Note: This happens automatically during normal updates -``` - -## Automatic Repository Switching - -### For Users - -Repository switching is now **fully automatic** for all dependencies. When you run `./update.sh`, the script: - -1. Fetches the central configuration from `.update-config.json` -2. Compares your current repository URLs with the preferred ones -3. Automatically switches if they differ (with confirmation in interactive mode) -4. Rebuilds containers with the new repositories - -You don't need to do anything special - just run your regular updates! - -### For Maintainers - -To switch all users to different repositories (e.g., when the fork merges upstream or dependencies change): - -1. **Edit `.update-config.json` in the main repository:** -```json -{ - "version": "1.1", - "description": "Central configuration for MeticAI update script", - "last_updated": "2026-01-13T21:38:00Z", - "repositories": { - "meticulous-mcp": { - "url": "https://github.com/meticulous/meticulous-mcp.git", - "description": "Meticulous MCP server for machine control" - }, - "meticai-web": { - "url": "https://github.com/your-org/MeticAI-web.git", - "description": "MeticAI web interface" - } - } -} -``` - -2. **Commit and push the change:** -```bash -git add .update-config.json -git commit -m "Switch to new repository URLs" -git push -``` - -3. **That's it!** When users run `./update.sh` or `./update.sh --auto`, they will: - - Fetch the updated configuration - - See notifications about any repository changes - - Automatically switch to the new repositories (with confirmation in interactive mode) - - Have their containers rebuilt with the new dependencies - -**Benefits:** -- Control all dependency repositories centrally -- No user intervention required (in auto mode) -- Safe backups created automatically -- Works with CI/CD pipelines -- Backward compatible with v1.0 config format - -## API Endpoint - -### GET /status - -Check for updates programmatically. - -**Request:** -```bash -curl http://YOUR_PI_IP:8000/status -``` - -**Response:** -```json -{ - "update_available": true, - "last_check": "2026-01-13T19:45:00Z", - "repositories": { - "meticai": { - "current_hash": "abc123...", - "last_updated": "2026-01-13T19:00:00Z" - }, - "meticulous-mcp": { - "current_hash": "def456...", - "repo_url": "https://github.com/hessius/meticulous-mcp.git", - "last_updated": "2026-01-13T18:00:00Z" - }, - "meticai-web": { - "current_hash": "ghi789...", - "last_updated": "2026-01-13T17:00:00Z" - } - } -} -``` - -## Version Tracking - -Updates are tracked in `.versions.json`: -- **Automatically generated** - Created on first run -- **Git-ignored** - Won't be committed to repository -- **JSON format** - Easy to parse programmatically -- **Timestamps** - Last check and update times -- **Commit hashes** - Precise version tracking - -## Troubleshooting - -### "Updates are available" but nothing changed - -This is expected when: -- Dependencies aren't installed yet (`meticulous-source` or `meticai-web` missing) -- Remote repository has new commits -- Repository URL has changed - -Solution: Run `./update.sh` without `--check-only` to apply updates. - -### Update script fails to pull changes - -Possible causes: -- Local modifications to dependency repositories -- Network connectivity issues -- Invalid git repository state - -Solutions: -1. Check network connection -2. Manually inspect dependency directories -3. Delete and re-clone: `rm -rf meticulous-source && ./update.sh` - -### Container rebuild fails - -If `docker compose up --build` fails: -- Check Docker is running -- Ensure you have sudo permissions (or are in docker group) -- Review Docker logs: `docker compose logs` - -### Version file is corrupted - -The update script handles this gracefully: -- Malformed JSON won't crash the script -- Script will create valid JSON on next successful run -- To reset: `rm .versions.json && ./update.sh --check-only` - -## Integration Examples - -### Web Application Polling - -```javascript -async function checkForUpdates() { - try { - const response = await fetch('http://YOUR_PI_IP:8000/status'); - const data = await response.json(); - - if (data.update_available) { - showNotification('Updates available!', 'Run ./update.sh to update'); - } - } catch (error) { - console.error('Failed to check for updates:', error); - } -} - -// Check every hour -setInterval(checkForUpdates, 3600000); -``` - -### Automated Updates (Cron) - -```bash -# Add to crontab to check daily at 3 AM -0 3 * * * cd /path/to/MeticAI && ./update.sh --check-only >> /var/log/meticai-updates.log 2>&1 -``` - -### CI/CD Integration - -```yaml -# Example GitHub Actions workflow -- name: Update dependencies - run: | - cd MeticAI - ./update.sh --auto -``` - -## Version-Based Updates - -MeticAI uses **semantic versioning** to control when users are notified about updates. Not every commit triggers an update notification - only when the maintainer explicitly bumps the version. - -### How It Works - -1. Each repository has a `VERSION` file containing the semantic version (e.g., `1.0.0`) -2. The update checker compares local `VERSION` with remote `VERSION` on GitHub -3. Users are only notified when the remote version is **greater than** their local version -4. Meticulous MCP (external dependency) still uses commit-based checking - -### For Maintainers: Releasing a New Version - -To release a new version that will trigger updates for all users: - -```bash -# 1. Update the VERSION file -echo "1.1.0" > VERSION - -# 2. Commit and push -git add VERSION -git commit -m "Release v1.1.0" -git push - -# 3. Optionally create a GitHub release/tag -git tag v1.1.0 -git push --tags -``` - -**For MeticAI-web**, do the same in the web repository: -```bash -cd meticai-web -echo "1.1.0" > VERSION -git add VERSION -git commit -m "Release v1.1.0" -git push -``` - -### Version Format - -- Use semantic versioning: `MAJOR.MINOR.PATCH` -- Examples: `1.0.0`, `1.1.0`, `2.0.0` -- The system compares versions numerically (e.g., `1.10.0` > `1.9.0`) - -## Best Practices - -1. **Check before updating**: Always run `--check-only` first -2. **Backup .env**: Your configuration is preserved, but backup is good practice -3. **Test after update**: Verify services are running after update -4. **Review changes**: Check what changed in updated repositories -5. **Monitor logs**: Use `docker compose logs -f` to monitor after rebuild - -## Security Considerations - -- Update script requires sudo for Docker operations -- Only clones from known repository URLs -- Doesn't modify `.env` file -- Creates backups when switching repositories -- All operations are logged in update output - -## Files Created - -| File | Purpose | Git Status | -|------|---------|------------| -| `update.sh` | Main update script | Tracked | -| `check-updates-on-start.sh` | Startup notification | Tracked | -| `VERSION` | Semantic version for releases | Tracked | -| `.versions.json` | Version tracking cache | Ignored | -| `tests/test_update.bats` | Test suite | Tracked | - -## Support - -If you encounter issues: -1. Check this document first -2. Review logs: `docker compose logs` -3. Run with verbose output: `bash -x update.sh --check-only` -4. Open an issue on GitHub with logs - -## License - -Same as MeticAI main project - see LICENSE file. diff --git a/check-updates-on-start.sh b/check-updates-on-start.sh deleted file mode 100755 index d38c998..0000000 --- a/check-updates-on-start.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -################################################################################ -# MeticAI - Startup Update Check -################################################################################ -# -# This script runs during container startup to check for updates and display -# a notification if updates are available. -# -################################################################################ - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Source common library -source "$SCRIPT_DIR/scripts/lib/common.sh" - -# Quick check for updates (non-interactive) -if [ -f "$SCRIPT_DIR/update.sh" ]; then - # Run update check in background to not slow down startup - { - sleep 5 # Wait for services to start - - # Run check-only mode - UPDATE_OUTPUT=$("$SCRIPT_DIR/update.sh" --check-only 2>&1) - - # Check if updates are available - if echo "$UPDATE_OUTPUT" | grep -q "Update available\|⚠\|Not installed"; then - echo "" - echo -e "${YELLOW}╔════════════════════════════════════════════════╗${NC}" - echo -e "${YELLOW}║ ║${NC}" - echo -e "${YELLOW}║ 📦 Updates Available for MeticAI! ║${NC}" - echo -e "${YELLOW}║ ║${NC}" - echo -e "${YELLOW}║ Run './update.sh' to update all components ║${NC}" - echo -e "${YELLOW}║ Or visit http://YOUR_IP:8000/docs ║${NC}" - echo -e "${YELLOW}║ and check the /status endpoint ║${NC}" - echo -e "${YELLOW}║ ║${NC}" - echo -e "${YELLOW}╚════════════════════════════════════════════════╝${NC}" - echo "" - fi - } & -fi diff --git a/docker-compose.old b/docker-compose.old deleted file mode 100644 index 825d7a4..0000000 --- a/docker-compose.old +++ /dev/null @@ -1,34 +0,0 @@ -services: - # 1. The Target Tool (Meticulous) - # We build this so it's available as an image on your system - meticulous: -# build: https://github.com/manonstreet/meticulous-mcp.git#main - build: ./meticulous-source - image: meticulous-mcp:latest - # We don't actually "run" this service as a daemon; - # Gemini will "run" it as a temporary command when needed. - entrypoint: ["tail", "-f", "/dev/null"] - - # 2. The Brain (Gemini CLI) - gemini-client: - build: ./gemini-client - container_name: gemini-client - volumes: - - /var/run/docker.sock:/var/run/docker.sock # CRITICAL: Allows it to run sibling containers - - ./gemini-client/settings.json:/root/.gemini/settings.json - env_file: - - .env - tty: true # Keeps the container running so you can attach to it - - # 3. The App (Coffee Relay) - coffee-relay: - build: ./coffee-relay - container_name: coffee-relay - ports: - - "8000:8000" - volumes: - # CRITICAL: Give it access to the host's Docker engine - - /var/run/docker.sock:/var/run/docker.sock - env_file: - - .env - restart: unless-stopped diff --git a/docker-up.sh b/docker-up.sh deleted file mode 100755 index 3042e0a..0000000 --- a/docker-up.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash -################################################################################ -# MeticAI - Docker Compose Wrapper -################################################################################ -# -# Use this script instead of running `docker compose` directly. -# It ensures proper file permissions are maintained. -# -# USAGE: -# ./docker-up.sh # Build and start all containers -# ./docker-up.sh --no-build # Start without building -# ./docker-up.sh down # Stop all containers -# -################################################################################ - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -# Source common library -source "$SCRIPT_DIR/scripts/lib/common.sh" - -# Ensure required files exist before Docker runs -prepare_files() { - # Ensure files exist (not directories) - [ -d ".versions.json" ] && rm -rf .versions.json - [ -d ".rebuild-needed" ] && rm -rf .rebuild-needed - [ ! -f ".versions.json" ] && echo '{}' > .versions.json - [ ! -f ".rebuild-needed" ] && touch .rebuild-needed - - # Pre-create directories - mkdir -p data logs -} - -# Fix permissions after Docker operations -fix_permissions() { - log_warning "Fixing file permissions..." - sudo chown -R "$(id -u):$(id -g)" data logs .versions.json .rebuild-needed \ - meticulous-source meticai-web 2>/dev/null || true - log_success "Permissions fixed" -} - -# Determine docker compose command -COMPOSE_CMD=$(get_compose_command) || exit 1 - -# Check if we need sudo -SUDO_PREFIX="" -if ! docker info &> /dev/null; then - if sudo docker info &> /dev/null; then - SUDO_PREFIX="sudo" - else - log_error "Cannot access Docker daemon" - exit 1 - fi -fi - -# Handle arguments -case "${1:-up}" in - down|stop) - $SUDO_PREFIX $COMPOSE_CMD down "$@" - ;; - *) - prepare_files - - if [[ "$1" == "--no-build" ]]; then - shift - $SUDO_PREFIX $COMPOSE_CMD up -d "$@" - else - $SUDO_PREFIX $COMPOSE_CMD up -d --build "$@" - fi - - # Fix permissions if we used sudo - if [ -n "$SUDO_PREFIX" ]; then - fix_permissions - fi - ;; -esac diff --git a/gemini-client/Dockerfile b/gemini-client/Dockerfile deleted file mode 100644 index 118f732..0000000 --- a/gemini-client/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM node:25-slim - -# Install curl so we can download the official script -RUN apt-get update && apt-get install -y curl git - -# Install the OFFICIAL Docker CLI (Latest) to match your host's API version -RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh - -# Install Gemini CLI -RUN npm install -g @google/gemini-cli - -# Install nanobanana extension for image generation -# Note: This requires a paid API key for image generation -# Clone to a known path so we can reference it in settings.json -RUN git clone https://github.com/gemini-cli-extensions/nanobanana.git /opt/nanobanana && \ - cd /opt/nanobanana && \ - npm install && \ - npm run build - -# Keep container alive -CMD ["tail", "-f", "/dev/null"] diff --git a/gemini-client/settings.json b/gemini-client/settings.json deleted file mode 100644 index b30bc21..0000000 --- a/gemini-client/settings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "theme": "GitHub", - "mcpServers": { - "meticulous": { - "command": "docker", - "args": [ - "exec", - "-i", - "meticulous-mcp-server", - "python3", - "-m", - "meticulous_mcp.server" - ] - }, - "nanobanana": { - "command": "node", - "args": ["/opt/nanobanana/mcp-server/dist/index.js"], - "env": {} - } - } -} diff --git a/gemini-client/settings.json.old b/gemini-client/settings.json.old deleted file mode 100644 index faad2f0..0000000 --- a/gemini-client/settings.json.old +++ /dev/null @@ -1,19 +0,0 @@ -{ - "theme": "GitHub", - "mcpServers": { - "meticulous": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", "METICULOUS_API_URL=http://192.168.50.168", - "-e", "PYTHONPATH=/app/meticulous-mcp/src:/app/pyMeticulous:/app/python-sdk/src", - "meticulous-mcp:latest", - "python", - "-u", - "/app/meticulous-mcp/run_server.py" - ] - } - } -} diff --git a/local-install.sh b/local-install.sh deleted file mode 100755 index 070f9a6..0000000 --- a/local-install.sh +++ /dev/null @@ -1,1632 +0,0 @@ -#!/bin/bash - -################################################################################ -# MeticAI - Interactive Installer -################################################################################ -# -# This script automates the installation of MeticAI, an autonomous AI agent -# that controls a Meticulous Espresso Machine using Google Gemini 2.0 Flash. -# -# USAGE: -# ./local-install.sh -# -# WHAT IT DOES: -# 1. Checks for prerequisites (Git, Docker) -# 2. Interactively collects configuration (API keys, IP addresses) -# 3. Creates .env file with your settings -# 4. Clones the required Meticulous MCP source repository -# 5. Builds and launches Docker containers -# -# REQUIREMENTS: -# - Git installed -# - Docker & Docker Compose installed -# - Google Gemini API key (get one at: https://aistudio.google.com/app/api-keys) -# - Meticulous Espresso Machine with known local IP address -# -################################################################################ - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Source common library -source "$SCRIPT_DIR/scripts/lib/common.sh" - -################################################################################ -# Non-Interactive Mode Support -################################################################################ -# Set METICAI_NON_INTERACTIVE=true to run without prompts. -# Required environment variables for non-interactive mode: -# - GEMINI_API_KEY: Your Google Gemini API key -# - METICULOUS_IP: IP address of your Meticulous machine -# - PI_IP: IP address of this server -# -# Optional environment variables: -# - METICAI_PROGRESS_FORMAT: Set to "platypus" for PROGRESS:X% format -# - SKIP_DOCK_SHORTCUT: Set to "true" to skip dock shortcut creation -# - SKIP_REBUILD_WATCHER: Set to "true" to skip watcher installation -# - SKIP_PREVIOUS_INSTALL_CHECK: Set to "true" to skip existing install check -# - FORCE_RECLONE: Set to "true" to force re-clone of repositories -################################################################################ - -# Progress output function (supports Platypus progress bar format) -show_progress() { - local message="$1" - local percent="${2:-}" - - if [ "$METICAI_PROGRESS_FORMAT" = "platypus" ]; then - if [ -n "$percent" ]; then - echo "PROGRESS:$percent" - fi - echo "$message" - else - echo -e "${YELLOW}$message${NC}" - fi -} - -# Check if running in non-interactive mode -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - # Validate required environment variables - if [ -z "$GEMINI_API_KEY" ]; then - echo "ERROR: GEMINI_API_KEY is required in non-interactive mode" >&2 - exit 1 - fi - if [ -z "$METICULOUS_IP" ]; then - echo "ERROR: METICULOUS_IP is required in non-interactive mode" >&2 - exit 1 - fi - if [ -z "$PI_IP" ]; then - echo "ERROR: PI_IP is required in non-interactive mode" >&2 - exit 1 - fi - - # Set defaults for non-interactive mode - SKIP_ENV_CREATION=false # Will create .env from environment variables - SKIP_PREVIOUS_INSTALL_CHECK="${SKIP_PREVIOUS_INSTALL_CHECK:-true}" - FORCE_RECLONE="${FORCE_RECLONE:-true}" - SKIP_DOCK_SHORTCUT="${SKIP_DOCK_SHORTCUT:-true}" - SKIP_REBUILD_WATCHER="${SKIP_REBUILD_WATCHER:-true}" - - show_progress "Running in non-interactive mode..." 5 -fi - -################################################################################ -# Sudo Helper Function -################################################################################ -# Some systems (like Puppy OS) are single-user and don't have sudo. -# This helper runs commands with sudo only when needed and available. -run_privileged() { - if [ "$(id -u)" -eq 0 ]; then - # Already root, no sudo needed - "$@" - elif command -v sudo &> /dev/null; then - # Not root but sudo is available - sudo "$@" - else - # Not root and no sudo - try anyway (will fail if permissions needed) - "$@" - fi -} - - - -# Only show banner in interactive mode -if [ "$METICAI_NON_INTERACTIVE" != "true" ]; then - log_info "=========================================" - log_info " ☕️ Barista AI Installer 🤖 " - log_info "=========================================" - echo "" -fi - -# Detect running MeticAI containers -detect_running_containers() { - if ! command -v docker &> /dev/null; then - return 1 # Docker not installed, no containers to detect - fi - - # Check for MeticAI-related containers (running or stopped) - local containers - containers=$(docker ps -a --format "{{.Names}}" 2>/dev/null | grep -E "(meticulous-mcp-server|gemini-client|meticai-server|meticai-web)" || true) - - if [ -n "$containers" ]; then - echo "$containers" - return 0 - else - return 1 - fi -} - -# Stop and remove MeticAI containers -stop_and_remove_containers() { - log_warning "Stopping and removing running MeticAI containers..." - - # Try docker compose down first (cleaner approach) - if [ -f "docker-compose.yml" ]; then - if docker compose down 2>/dev/null || docker-compose down 2>/dev/null; then - log_success "Containers stopped and removed via docker compose" - return 0 - fi - fi - - # Fallback: Remove containers individually - local containers - containers=$(docker ps -a --format "{{.Names}}" 2>/dev/null | grep -E "(meticulous-mcp-server|gemini-client|meticai-server|meticai-web)" || true) - - if [ -n "$containers" ]; then - while IFS= read -r container; do - log_warning " Stopping and removing: $container" - docker stop "$container" 2>/dev/null || true - docker rm "$container" 2>/dev/null || true - done < <(echo "$containers") - log_success "Individual containers stopped and removed" - fi -} - -# Detect previous MeticAI installation artifacts -detect_previous_installation() { - local found_items=() - local current_dir="$(pwd)" - - # Check for typical MeticAI installation artifacts - # Note: .update-config.json is a source file and NOT an installation artifact - [ -f ".env" ] && found_items+=(".env file at $current_dir/.env") - [ -d "meticulous-source" ] && found_items+=("meticulous-source directory at $current_dir/meticulous-source") - [ -d "meticai-web" ] && found_items+=("meticai-web directory at $current_dir/meticai-web") - [ -f ".versions.json" ] && found_items+=(".versions.json file at $current_dir/.versions.json") - [ -f ".rebuild-needed" ] && found_items+=(".rebuild-needed file at $current_dir/.rebuild-needed") - - # Check for macOS-specific installations - if [[ "$OSTYPE" == "darwin"* ]]; then - [ -d "/Applications/MeticAI.app" ] && found_items+=("macOS Dock shortcut at /Applications/MeticAI.app") - [ -f "$HOME/Library/LaunchAgents/com.meticai.rebuild-watcher.plist" ] && found_items+=("rebuild watcher service (launchd) at $HOME/Library/LaunchAgents/com.meticai.rebuild-watcher.plist") - fi - - # Check for Linux-specific installations (Raspberry Pi, etc.) - if [[ "$OSTYPE" == "linux"* ]]; then - [ -f "/etc/systemd/system/meticai-rebuild-watcher.path" ] && found_items+=("rebuild watcher service (systemd) at /etc/systemd/system/meticai-rebuild-watcher.path") - fi - - if [ ${#found_items[@]} -gt 0 ]; then - # Return items line-separated to handle items with spaces - printf '%s\n' "${found_items[@]}" - return 0 - else - return 1 - fi -} - -# Check for running containers and previous installations -# Skip in non-interactive mode if SKIP_PREVIOUS_INSTALL_CHECK is set -if [ "$SKIP_PREVIOUS_INSTALL_CHECK" != "true" ]; then - log_warning "Checking for existing MeticAI installations..." - echo "" - - CONTAINERS_FOUND="" - PREVIOUS_INSTALL_FOUND="" - - # Detect running containers - if CONTAINERS_FOUND=$(detect_running_containers); then - log_warning "Found running MeticAI containers:" - echo "$CONTAINERS_FOUND" | sed 's/^/ - /' - echo "" - fi - - # Detect previous installation artifacts - if PREVIOUS_INSTALL_FOUND=$(detect_previous_installation); then - log_warning "Found existing MeticAI installation artifacts:" - # Items are returned line-separated, so we can process them directly - echo "$PREVIOUS_INSTALL_FOUND" | while IFS= read -r item; do - echo " - $item" - done - echo "" - fi - - # If we found either containers or previous installation, offer uninstall - if [ -n "$CONTAINERS_FOUND" ] || [ -n "$PREVIOUS_INSTALL_FOUND" ]; then - log_warning "=========================================" - log_warning " Previous Installation Detected" - log_warning "=========================================" - echo "" - log_info "It looks like MeticAI may already be installed or partially installed." - echo "" - log_warning "Recommended actions:" - echo -e " 1) Run the uninstall script first to clean up: ${BLUE}./uninstall.sh${NC}" - echo -e " 2) Then run this installer again for a fresh installation" - echo "" - log_warning "Or:" - echo -e " 3) Continue anyway (may cause conflicts or use existing configuration)" - echo "" - - # Check if uninstall script exists - if [ -f "./uninstall.sh" ]; then - read -r -p "Would you like to run the uninstall script now? (y/n) [y]: " RUN_UNINSTALL /dev/null)" ]; then - log_success "Found existing data directory with profile history - will preserve it" -fi - -# In non-interactive mode, skip .env check prompts -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - # Non-interactive mode: always create new .env from environment variables - SKIP_ENV_CREATION=false -elif [ -f ".env" ]; then - log_warning "Found existing .env file (preserved from previous installation)." - echo "" - cat .env - echo "" - read -r -p "Do you want to use this existing configuration? (y/n) [y]: " USE_EXISTING_ENV /dev/null; then - log_warning "Installing qrencode..." - if brew install qrencode &> /dev/null; then - log_success "qrencode installed successfully." - return 0 - else - log_warning "Failed to install qrencode via Homebrew." - return 1 - fi - else - return 1 - fi - ;; - ubuntu|debian|raspbian) - log_warning "Installing qrencode..." - # Update package cache for more reliable installation - if run_privileged apt-get update &> /dev/null && run_privileged apt-get install -y qrencode &> /dev/null; then - log_success "qrencode installed successfully." - return 0 - else - log_warning "Failed to install qrencode." - return 1 - fi - ;; - fedora|rhel|centos) - log_warning "Installing qrencode..." - if command -v dnf &> /dev/null; then - if run_privileged dnf install -y qrencode &> /dev/null; then - log_success "qrencode installed successfully." - return 0 - fi - elif command -v yum &> /dev/null; then - if run_privileged yum install -y qrencode &> /dev/null; then - log_success "qrencode installed successfully." - return 0 - fi - fi - log_warning "Failed to install qrencode." - return 1 - ;; - arch|manjaro) - log_warning "Installing qrencode..." - # Use -S instead of -Sy to avoid slow database sync - if run_privileged pacman -S --noconfirm qrencode &> /dev/null; then - log_success "qrencode installed successfully." - return 0 - else - log_warning "Failed to install qrencode." - return 1 - fi - ;; - *) - return 1 - ;; - esac -} - -# Generate and display ASCII QR code for a URL -generate_qr_code() { - local url="$1" - - echo "" - log_success "=========================================" - log_success " 📱 Scan to Access Web App 📱 " - log_success "=========================================" - echo "" - - # Try to use qrencode if available (common on many Linux systems) - if command -v qrencode &> /dev/null; then - qrencode -t ansiutf8 "$url" 2>/dev/null - echo "" - log_warning "Scan the QR code above to open MeticAI Web App" - log_warning "Or visit directly: ${BLUE}${url}${NC}" - echo "" - return - fi - - # If qrencode not found, try to install it automatically - log_warning "QR code generator not found. Attempting to install..." - if install_qrencode && command -v qrencode &> /dev/null; then - # Installation succeeded, generate QR code - qrencode -t ansiutf8 "$url" 2>/dev/null - echo "" - log_warning "Scan the QR code above to open MeticAI Web App" - log_warning "Or visit directly: ${BLUE}${url}${NC}" - echo "" - return - fi - - # Try Python with qrcode library (if available) - if command -v python3 &> /dev/null; then - local python_result - python_result=$(python3 -c " -try: - import qrcode - qr = qrcode.QRCode() - qr.add_data('$url') - qr.print_ascii() - print('SUCCESS') -except: - print('FAILED') -" 2>/dev/null) - - if echo "$python_result" | grep -q "SUCCESS"; then - echo "$python_result" | grep -v "SUCCESS" - echo "" - log_warning "Scan the QR code above to open MeticAI Web App" - log_warning "Or visit directly: ${BLUE}${url}${NC}" - echo "" - return - fi - fi - - # Fallback: Show a simple box with the URL - log_warning "┌──────────────────────────────────────────────┐" - log_warning "│ │" - log_warning "│ Open this URL on your mobile device: │" - log_warning "│ │" - log_warning "│ ${BLUE}${url}${YELLOW}│" - log_warning "│ │" - log_warning "│ 💡 QR code not available on this system │" - log_warning "│ │" - log_warning "└──────────────────────────────────────────────┘" - echo "" -} - -# Install the rebuild watcher service on macOS for automatic web UI updates -install_rebuild_watcher() { - local script_dir="$1" - local watcher_script="${script_dir}/rebuild-watcher.sh" - - if [ ! -f "$watcher_script" ]; then - log_warning "Warning: rebuild-watcher.sh not found, skipping." - return 1 - fi - - log_warning "Installing rebuild watcher service..." - log_info "This enables fully automatic updates from the web interface." - - # Ensure the script is executable - chmod +x "$watcher_script" - - # Create empty signal files for Docker mount - touch "${script_dir}/.rebuild-needed" - touch "${script_dir}/.update-check-requested" - touch "${script_dir}/.update-requested" - touch "${script_dir}/.restart-requested" - touch "${script_dir}/.rebuild-watcher.log" - - # Configure git safe.directory for root (needed when systemd runs as root) - # This prevents "dubious ownership" errors - log_info "Configuring git safe directories for systemd..." - run_privileged git config --global --add safe.directory "${script_dir}" 2>/dev/null || true - run_privileged git config --global --add safe.directory "${script_dir}/meticulous-source" 2>/dev/null || true - run_privileged git config --global --add safe.directory "${script_dir}/meticai-web" 2>/dev/null || true - - # Run the install command - if "$watcher_script" --install; then - log_success "Rebuild watcher installed successfully" - log_info " Updates triggered from the web UI will now automatically rebuild containers." - return 0 - else - log_warning "Warning: Failed to install rebuild watcher service." - log_warning " You can install it manually later with: ./rebuild-watcher.sh --install" - return 1 - fi -} - -# Create macOS .app bundle for dock shortcut -create_macos_dock_shortcut() { - local url="$1" - local app_name="MeticAI" - local app_path="/Applications/${app_name}.app" - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local icon_source="${script_dir}/resources/MeticAI.icns" - - # Validate URL format (basic check for http/https) - if [[ ! "$url" =~ ^https?:// ]]; then - log_error "Error: Invalid URL format. Skipping dock shortcut creation." - return 1 - fi - - echo "" - log_warning "Creating macOS application and adding to Dock..." - - # Create application bundle structure (may need elevated permissions for /Applications) - if ! mkdir -p "${app_path}/Contents/MacOS" 2>/dev/null; then - # Try with run_privileged (handles sudo/no-sudo) - run_privileged mkdir -p "${app_path}/Contents/MacOS" - run_privileged mkdir -p "${app_path}/Contents/Resources" - NEED_SUDO=true - else - mkdir -p "${app_path}/Contents/Resources" - NEED_SUDO=false - fi - - # Copy the icon if it exists - if [ -f "$icon_source" ]; then - if [ "$NEED_SUDO" = true ]; then - run_privileged cp "$icon_source" "${app_path}/Contents/Resources/MeticAI.icns" - else - cp "$icon_source" "${app_path}/Contents/Resources/MeticAI.icns" - fi - local icon_key="CFBundleIconFile - MeticAI" - else - local icon_key="" - fi - - # Create the executable script with properly escaped URL - # Using printf to avoid shell expansion issues - local script_content="#!/bin/bash -# MeticAI Web App Launcher -open \"${url}\" -" - if [ "$NEED_SUDO" = true ]; then - echo "$script_content" | run_privileged tee "${app_path}/Contents/MacOS/${app_name}" > /dev/null - run_privileged chmod +x "${app_path}/Contents/MacOS/${app_name}" - else - echo "$script_content" > "${app_path}/Contents/MacOS/${app_name}" - chmod +x "${app_path}/Contents/MacOS/${app_name}" - fi - - # Create Info.plist with icon reference if available - local plist_content=" - - - - CFBundleExecutable - ${app_name} - ${icon_key} - CFBundleIdentifier - com.meticai.webapp - CFBundleName - ${app_name} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSMinimumSystemVersion - 10.15 - NSHighResolutionCapable - - -" - - if [ "$NEED_SUDO" = true ]; then - echo "$plist_content" | run_privileged tee "${app_path}/Contents/Info.plist" > /dev/null - else - echo "$plist_content" > "${app_path}/Contents/Info.plist" - fi - - log_success "Application created at: ${app_path}" - - # Add to Dock using defaults command - # First check if it's already in the Dock - local dock_plist="$HOME/Library/Preferences/com.apple.dock.plist" - if ! /usr/libexec/PlistBuddy -c "Print :persistent-apps" "$dock_plist" 2>/dev/null | grep -q "MeticAI"; then - # Add the app to the Dock - defaults write com.apple.dock persistent-apps -array-add " - tile-data - - file-data - - _CFURLString - file://${app_path}/ - _CFURLStringType - 15 - - - " - - # Restart the Dock to apply changes - killall Dock - - log_success "MeticAI added to your Dock" - else - log_warning " MeticAI is already in your Dock" - fi -} - -# Detect OS -detect_os() { - if [[ "$OSTYPE" == "darwin"* ]]; then - OS="macos" - elif [ -f /etc/os-release ]; then - # shellcheck disable=SC1091 - . /etc/os-release - OS=$ID - elif [ -f /etc/redhat-release ]; then - OS="rhel" - else - OS="unknown" - fi - echo "$OS" -} - -# Install git based on OS -install_git() { - local os - os=$(detect_os) - log_warning "Installing git..." - - case "$os" in - macos) - if command -v brew &> /dev/null; then - if brew install git; then - log_success "Git installed successfully." - else - log_error "Failed to install git via Homebrew." - exit 1 - fi - else - log_warning "Homebrew not found. Installing Homebrew first..." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - - # Source Homebrew environment for both Intel and Apple Silicon Macs - if [ -f /opt/homebrew/bin/brew ]; then - eval "$(/opt/homebrew/bin/brew shellenv)" - elif [ -f /usr/local/bin/brew ]; then - eval "$(/usr/local/bin/brew shellenv)" - fi - - if command -v brew &> /dev/null && brew install git; then - log_success "Git installed successfully." - else - log_error "Failed to install git. Please install manually." - echo "Visit: https://git-scm.com/downloads" - exit 1 - fi - fi - ;; - ubuntu|debian|raspbian) - if run_privileged apt-get update && run_privileged apt-get install -y git; then - log_success "Git installed successfully." - else - log_error "Failed to install git. Please install manually." - exit 1 - fi - ;; - fedora|rhel|centos) - if command -v dnf &> /dev/null; then - if run_privileged dnf install -y git; then - log_success "Git installed successfully." - else - log_error "Failed to install git. Please install manually." - exit 1 - fi - elif command -v yum &> /dev/null; then - if run_privileged yum install -y git; then - log_success "Git installed successfully." - else - log_error "Failed to install git. Please install manually." - exit 1 - fi - else - log_error "No supported package manager found. Please install git manually." - exit 1 - fi - ;; - arch|manjaro) - if run_privileged pacman -Sy --noconfirm git; then - log_success "Git installed successfully." - else - log_error "Failed to install git. Please install manually." - exit 1 - fi - ;; - *) - log_error "Unsupported OS for automatic installation. Please install git manually." - echo "Visit: https://git-scm.com/downloads" - exit 1 - ;; - esac -} - -# Install docker based on OS -install_docker() { - local os - os=$(detect_os) - log_warning "Installing Docker..." - - if [[ "$os" == "macos" ]]; then - log_warning "On macOS, Docker Desktop must be installed manually." - log_warning "Please visit: https://www.docker.com/products/docker-desktop" - log_warning "After installing Docker Desktop, make sure it's running and try again." - exit 1 - fi - - # Use Docker's official convenience script - if command -v curl &> /dev/null; then - curl -fsSL https://get.docker.com -o /tmp/get-docker.sh - run_privileged sh /tmp/get-docker.sh - rm /tmp/get-docker.sh - - # Add current user to docker group (only if not already root) - if [ "$(id -u)" -ne 0 ]; then - run_privileged usermod -aG docker "$USER" || true - fi - - # Start docker service - run_privileged systemctl enable docker || true - run_privileged systemctl start docker || true - - log_success "Docker installed successfully." - log_warning "Note: You may need to log out and back in for docker group changes to take effect." - else - log_error "curl is required to install Docker. Please install curl first." - exit 1 - fi -} - -# Install docker compose plugin -install_docker_compose() { - local os - os=$(detect_os) - log_warning "Installing Docker Compose..." - - if [[ "$os" == "macos" ]]; then - log_warning "On macOS, Docker Compose comes bundled with Docker Desktop." - log_warning "If Docker Desktop is installed and running, Docker Compose should be available." - if check_docker_compose; then - log_success "Docker Compose is available." - return 0 - else - log_error "Docker Compose not found. Please make sure Docker Desktop is installed and running." - log_warning "Visit: https://www.docker.com/products/docker-desktop" - exit 1 - fi - fi - - # Try to install docker-compose-plugin (preferred method) - case "$os" in - ubuntu|debian|raspbian) - if run_privileged apt-get update; then - if run_privileged apt-get install -y docker-compose-plugin; then - log_success "Docker Compose plugin installed." - fi - fi - ;; - fedora|rhel|centos) - if command -v dnf &> /dev/null; then - run_privileged dnf install -y docker-compose-plugin - elif command -v yum &> /dev/null; then - run_privileged yum install -y docker-compose-plugin - fi - ;; - *) - # Fallback to standalone docker-compose - log_warning "Installing standalone docker-compose..." - run_privileged curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - run_privileged chmod +x /usr/local/bin/docker-compose - ;; - esac - - if check_docker_compose; then - log_success "Docker Compose installed successfully." - else - log_error "Failed to install Docker Compose. Please install manually." - exit 1 - fi -} - -# Detect server IP address (cross-platform) -detect_ip() { - local detected_ip="" - - # Try macOS-specific methods first - if [[ "$OSTYPE" == "darwin"* ]]; then - # Try to get IP from default route - detected_ip=$(route -n get default 2>/dev/null | grep 'interface:' | awk '{print $2}' | xargs ipconfig getifaddr 2>/dev/null) - - if [ -z "$detected_ip" ]; then - # Fallback: Try ipconfig getifaddr for common interfaces - for interface in en0 en1 en2 en3 en4; do - detected_ip=$(ipconfig getifaddr "$interface" 2>/dev/null) - if [ -n "$detected_ip" ]; then - echo "$detected_ip" - return - fi - done - fi - - if [ -z "$detected_ip" ]; then - # Last resort: use ifconfig and parse - detected_ip=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -1) - fi - else - # Linux: use hostname -I - detected_ip=$(hostname -I 2>/dev/null | awk '{print $1}') - fi - - echo "$detected_ip" -} - -# Portable timeout function that works on both macOS and Linux -# Usage: run_with_timeout [args...] -run_with_timeout() { - local timeout_secs="$1" - shift - - # Try GNU timeout first (works on Linux, available as gtimeout on macOS with coreutils) - if command -v timeout &>/dev/null; then - timeout "$timeout_secs" "$@" 2>/dev/null - return $? - elif command -v gtimeout &>/dev/null; then - gtimeout "$timeout_secs" "$@" 2>/dev/null - return $? - else - # Fallback: use perl alarm (available on macOS by default) - perl -e 'alarm shift; exec @ARGV' "$timeout_secs" "$@" 2>/dev/null - return $? - fi -} - -# Resolve .local hostname to IP address (cross-platform) -resolve_local_hostname() { - local hostname="$1" - local resolved_ip="" - - # Ensure hostname ends with .local - if [[ ! "$hostname" =~ \.local$ ]]; then - hostname="${hostname}.local" - fi - - # Method 1: macOS dscacheutil (most reliable for .local on macOS) - if command -v dscacheutil &>/dev/null; then - resolved_ip=$(dscacheutil -q host -a name "$hostname" 2>/dev/null | grep "^ip_address:" | head -1 | awk '{print $2}') - if [[ -n "$resolved_ip" ]]; then - echo "$resolved_ip" - return 0 - fi - fi - - # Method 2: getent (Linux) - if command -v getent &>/dev/null; then - resolved_ip=$(getent hosts "$hostname" 2>/dev/null | awk '{print $1}' | head -1) - if [[ -n "$resolved_ip" ]]; then - echo "$resolved_ip" - return 0 - fi - fi - - # Method 3: ping (works on both, but slower) - if command -v ping &>/dev/null; then - # macOS ping uses -t for timeout, Linux uses -W - if [[ "$OSTYPE" == "darwin"* ]]; then - resolved_ip=$(ping -c 1 -t 2 "$hostname" 2>/dev/null | grep -oE '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' | head -1 | tr -d '()') - else - resolved_ip=$(ping -c 1 -W 2 "$hostname" 2>/dev/null | grep -oE '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' | head -1 | tr -d '()') - fi - if [[ -n "$resolved_ip" ]]; then - echo "$resolved_ip" - return 0 - fi - fi - - return 1 -} - -# Scan network for Meticulous machines -# Returns list of "hostname,ip" pairs -scan_for_meticulous() { - local devices=() - - # Method 1: macOS dns-sd (Bonjour) - most reliable on macOS - if [[ "$OSTYPE" == "darwin"* ]] && command -v dns-sd &>/dev/null; then - # Browse for _http._tcp services and capture meticulous devices - local dns_sd_output - dns_sd_output=$(run_with_timeout 4 dns-sd -B _http._tcp local 2>/dev/null || true) - - # Parse dns-sd output to find meticulous devices - # Format: "Timestamp A/R Flags if Domain Service Type Instance Name" - if [ -n "$dns_sd_output" ]; then - while IFS= read -r line; do - # Look for lines containing "meticulous" (case insensitive) - if echo "$line" | grep -qi "meticulous"; then - # Extract the instance name (last field, may contain spaces) - # The format is: timestamp Add/Remove flags interface domain type instancename - local instance_name - instance_name=$(echo "$line" | awk '{for(i=7;i<=NF;i++) printf "%s", (i>7?" ":"") $i; print ""}') - - if [[ -n "$instance_name" ]]; then - # Resolve the hostname to IP using dscacheutil - local resolved_ip - resolved_ip=$(resolve_local_hostname "$instance_name") - - if [[ -n "$resolved_ip" ]]; then - devices+=("${instance_name}.local,$resolved_ip") - fi - fi - fi - done <<< "$dns_sd_output" - fi - fi - - # Method 2: Try avahi-browse (Linux mDNS/Bonjour) - if command -v avahi-browse &>/dev/null && [[ ${#devices[@]} -eq 0 ]]; then - # Scan for _http._tcp services with a timeout - local avahi_results - avahi_results=$(run_with_timeout 5 avahi-browse -a -t -r -p 2>/dev/null | grep -i meticulous || true) - - if [ -n "$avahi_results" ]; then - # Parse avahi output: format is =;interface;protocol;name;type;domain;hostname;address;port;txt - while IFS= read -r line; do - if [[ "$line" == =* ]]; then - local hostname=$(echo "$line" | cut -d';' -f7) - local ip=$(echo "$line" | cut -d';' -f8) - if [[ "$hostname" =~ meticulous ]] && [[ -n "$ip" ]]; then - devices+=("$hostname,$ip") - fi - fi - done <<< "$avahi_results" - fi - fi - - # Method 3: Try ARP cache (works on both Linux and macOS) - if [[ ${#devices[@]} -eq 0 ]]; then - if command -v arp &>/dev/null; then - # Get all IPs from ARP cache and try to resolve hostnames - local arp_ips - arp_ips=$(arp -a 2>/dev/null | grep -oE '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' | tr -d '()' || true) - - for ip in $arp_ips; do - # Try to get hostname via various methods - local hostname="" - - # macOS: try dscacheutil reverse lookup - if [[ "$OSTYPE" == "darwin"* ]] && command -v dscacheutil &>/dev/null; then - hostname=$(dscacheutil -q host -a ip_address "$ip" 2>/dev/null | grep "^name:" | head -1 | awk '{print $2}' || true) - fi - - # Linux: try getent - if [[ -z "$hostname" ]] && command -v getent &>/dev/null; then - hostname=$(getent hosts "$ip" 2>/dev/null | awk '{print $2}' || true) - fi - - # Fallback: nslookup - if [[ -z "$hostname" ]] && command -v nslookup &>/dev/null; then - hostname=$(nslookup "$ip" 2>/dev/null | grep 'name =' | awk '{print $4}' | sed 's/\.$//' || true) - fi - - # Fallback: host command - if [[ -z "$hostname" ]] && command -v host &>/dev/null; then - hostname=$(host "$ip" 2>/dev/null | grep 'domain name pointer' | awk '{print $5}' | sed 's/\.$//' || true) - fi - - # Check if hostname contains "meticulous" - if [[ -n "$hostname" ]] && echo "$hostname" | grep -qi "meticulous"; then - devices+=("$hostname,$ip") - fi - done - fi - fi - - # Method 4: Try scanning .local mDNS domain directly - if [[ ${#devices[@]} -eq 0 ]]; then - # Try common meticulous hostnames patterns - for name in meticulous meticulousmodelalmondmilklatte meticulousInspiringCoffeeGeek; do - local resolved_ip - resolved_ip=$(resolve_local_hostname "$name") - - if [[ -n "$resolved_ip" ]]; then - devices+=("${name}.local,$resolved_ip") - fi - done - fi - - # Return unique devices - if [ ${#devices[@]} -gt 0 ]; then - printf '%s\n' "${devices[@]}" | sort -u - fi -} - -# 1. Check for Prerequisites -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Checking prerequisites..." 10 -else - log_warning "[1/4] Checking and installing prerequisites..." -fi - -# Check and install git -if ! command -v git &> /dev/null; then - if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - # Auto-install in non-interactive mode - show_progress "Installing git..." 12 - install_git - else - log_error "Error: git is not installed." - read -r -p "Would you like to install git now? (y/n) [y]: " INSTALL_GIT /dev/null; then - if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - echo "ERROR: Docker is not installed. Please install Docker Desktop first." - exit 1 - else - log_error "Error: docker is not installed." - read -r -p "Would you like to install Docker now? (y/n) [y]: " INSTALL_DOCKER /dev/null; then - if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - # Skip qrencode in non-interactive mode - it's optional - : # no-op - else - log_warning "qrencode is not installed (used for QR code generation)." - read -r -p "Would you like to install qrencode now? (y/n) [y]: " INSTALL_QRENCODE .env -GEMINI_API_KEY=$GEMINI_KEY -METICULOUS_IP=$MET_IP -PI_IP=$PI_IP -EOF - show_progress "Configuration created" 25 -else - log_warning "[2/4] Configuration" - echo "We need to create a .env file with your specific settings." - echo "" - - # --- Gemini API Key --- - # Get your free API key at: https://aistudio.google.com/app/api-keys - echo "Get your free API key at: https://aistudio.google.com/app/api-keys" - read -r -p "Enter your Google Gemini API Key: " GEMINI_KEY .env -GEMINI_API_KEY=$GEMINI_KEY -METICULOUS_IP=$MET_IP -PI_IP=$PI_IP -EOF - log_success ".env file created." - echo "" -fi - -# 3. Setup Dependencies (The MCP Fork & Web App) -################################################################################ -# Clone the Meticulous MCP server fork required for machine communication -# Repository: https://github.com/hessius/meticulous-mcp.git -# And the MeticAI Web Interface -# Repository: https://github.com/hessius/MeticAI-web.git -################################################################################ -log_warning "[3/4] Setting up Meticulous Source and Web App..." - -# Clone MCP Source -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Setting up dependencies..." 30 -fi -if [ -d "meticulous-source" ]; then - if [ "$FORCE_RECLONE" = "true" ]; then - # Non-interactive mode: force re-clone - show_progress "Removing old meticulous-source..." 35 - rm -rf meticulous-source - show_progress "Cloning meticulous-source..." 40 - git clone https://github.com/hessius/meticulous-mcp.git meticulous-source - elif [ "$METICAI_NON_INTERACTIVE" != "true" ]; then - echo "Directory 'meticulous-source' already exists." - read -r -p "Do you want to delete it and re-clone the latest version? (y/n) [n]: " CLONE_CONFIRM meticai-web/public/config.json -{ - "serverUrl": "http://$PI_IP:8000" -} -WEBCONFIG -# Also create in meticai-web root for standalone docker-compose usage -cp meticai-web/public/config.json meticai-web/config.json -log_success "Web app configured." -echo "" - -# 4. Build and Launch -################################################################################ -# Stop any existing containers, then build and start the Docker services -# This includes: -# - meticai-server: FastAPI server for receiving requests -# - gemini-client: AI brain using Google Gemini 2.0 Flash -################################################################################ - -# Load .env values for display and web config generation -if [ -f ".env" ]; then - # Source the .env file to get the values - set -a # automatically export all variables - source .env - set +a - - # Ensure variables are set (fallback for any issues) - : ${PI_IP:="localhost"} - : ${MET_IP:="localhost"} -fi - -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Building and launching containers..." 60 -else - log_warning "[4/4] Building and Launching Containers..." - if [ "$(id -u)" -eq 0 ]; then - echo "Note: Running as root." - elif command -v sudo &> /dev/null; then - echo "Note: Running with sudo permissions." - else - echo "Note: Running without elevated permissions." - fi -fi - -# Ensure .versions.json exists as a file (not directory) before Docker mounts it -# Docker will create a directory if the file doesn't exist, causing mount errors -if [ -d ".versions.json" ]; then - if [ "$METICAI_NON_INTERACTIVE" != "true" ]; then - log_warning "Fixing .versions.json (was directory, converting to file)..." - fi - rm -rf .versions.json -fi -if [ ! -f ".versions.json" ]; then - echo '{}' > .versions.json -fi - -# Ensure .rebuild-needed exists as a file for the trigger-update endpoint -if [ -d ".rebuild-needed" ]; then - rm -rf .rebuild-needed -fi -if [ ! -f ".rebuild-needed" ]; then - touch .rebuild-needed -fi - -# Ensure .update-check-requested exists as a file for the check-updates endpoint -if [ -d ".update-check-requested" ]; then - rm -rf .update-check-requested -fi -if [ ! -f ".update-check-requested" ]; then - touch .update-check-requested -fi - -# Ensure .update-requested exists as a file for the trigger-update endpoint -if [ -d ".update-requested" ]; then - rm -rf .update-requested -fi -if [ ! -f ".update-requested" ]; then - touch .update-requested -fi - -# Ensure .restart-requested exists as a file for the restart endpoint -if [ -d ".restart-requested" ]; then - rm -rf .restart-requested -fi -if [ ! -f ".restart-requested" ]; then - touch .restart-requested -fi - -# Ensure .rebuild-watcher.log exists as a file for the watcher status endpoint -if [ -d ".rebuild-watcher.log" ]; then - rm -rf .rebuild-watcher.log -fi -if [ ! -f ".rebuild-watcher.log" ]; then - touch .rebuild-watcher.log -fi - -# Pre-create directories that Docker would otherwise create as root -# This ensures proper ownership for the current user -mkdir -p data logs - -# Final safety check: ensure config.json files are not directories -# This catches cases where a previous failed install left directories behind -if [ -d "meticai-web/config.json" ]; then - rm -rf meticai-web/config.json - echo '{"serverUrl": "http://'"$PI_IP"':8000"}' > meticai-web/config.json -fi -if [ -d "meticai-web/public/config.json" ]; then - rm -rf meticai-web/public/config.json - echo '{"serverUrl": "http://'"$PI_IP"':8000"}' > meticai-web/public/config.json -fi - -if [ "$METICAI_NON_INTERACTIVE" != "true" ]; then - log_success "Data directories created with correct ownership" -fi - -# Stop existing containers if running (safety net in case any were missed earlier) -# This handles edge cases where containers might have been started after detection -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Stopping existing containers..." 65 -fi - -# On macOS with Docker Desktop, we don't need sudo for docker commands -# On Linux, we may need sudo if user isn't in docker group (but not on single-user systems) -if [[ "$OSTYPE" == "darwin"* ]]; then - docker compose down 2>/dev/null || true -else - run_privileged docker compose down 2>/dev/null || true -fi - -# Build and start -if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Building containers (this may take a few minutes)..." 70 -fi - -# Determine the docker command to use -# On macOS: always use docker directly (Docker Desktop handles permissions) -# On Linux: use run_privileged to handle both sudo and non-sudo environments -if [[ "$OSTYPE" == "darwin"* ]]; then - DOCKER_CMD="docker compose" -else - # Build the command dynamically to handle sudo/no-sudo - if [ "$(id -u)" -eq 0 ]; then - DOCKER_CMD="docker compose" - elif command -v sudo &> /dev/null; then - DOCKER_CMD="sudo docker compose" - else - DOCKER_CMD="docker compose" - fi -fi - -if $DOCKER_CMD up -d --build; then - if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Finalizing installation..." 90 - fi - - # Fix ownership of any files created by Docker running as root - # This is needed on Linux because sudo docker compose creates files as root - # On macOS with Docker Desktop, this is usually not necessary but doesn't hurt - # On single-user systems running as root, this is a no-op - if [ "$METICAI_NON_INTERACTIVE" != "true" ]; then - echo "Fixing file ownership..." - fi - if [[ "$OSTYPE" != "darwin"* ]] && [ "$(id -u)" -ne 0 ]; then - run_privileged chown -R "$(id -u):$(id -g)" data logs .versions.json .rebuild-needed .update-check-requested .update-requested 2>/dev/null || true - # Also fix git directories in case they were affected - run_privileged chown -R "$(id -u):$(id -g)" meticulous-source meticai-web 2>/dev/null || true - fi - - if [ "$METICAI_NON_INTERACTIVE" = "true" ]; then - show_progress "Installation complete!" 100 - else - echo "" - log_success "=========================================" - log_success " 🎉 Installation Complete! 🎉 " - log_success "=========================================" - echo "" - fi - echo "Your Barista Agent is running." - echo "" - echo -e "👉 **Web Interface:** http://$PI_IP:3550" - echo -e "👉 **Relay API:** http://$PI_IP:8000" - echo -e "👉 **Meticulous:** http://$MET_IP (via Agent)" - echo "" - - # Display QR code for easy mobile access - generate_qr_code "http://$PI_IP:3550" - - # Offer macOS dock shortcut creation (check if /dev/tty is available for input) - if [[ "$OSTYPE" == "darwin"* ]] && [[ -c /dev/tty ]]; then - # Check if user wants to skip via environment variable - if [[ "${SKIP_DOCK_SHORTCUT}" != "true" ]]; then - echo "" - read -r -p "Would you like to add MeticAI to your Dock? (y/n) [y]: " CREATE_DOCK_SHORTCUT CFBundleDisplayName -${APP_NAME} -LSUIElement - -``` - -**Impact:** -- Better icon display in Finder -- Proper app naming in macOS UI -- Not hidden from Dock/App Switcher - -**Note:** AppleScript dialogs don't inherit app icons - this is a macOS limitation. The icon appears in Finder/Dock but not in individual dialogs. - -### Fix 4: Debug Logging - -**File Modified:** `macos-installer/install-wrapper.sh` - -**Changes:** -Added comprehensive logging to prerequisite check: -```bash -log_message "Checking prerequisites..." -log_message "PATH: $PATH" -log_message "Git found at: $(command -v git)" -log_message "Docker found at: $docker_path" -log_message "Docker daemon is running" -log_message "Prerequisite check complete..." -``` - -**Impact:** -- Users can see what's being detected -- Easier troubleshooting -- Progress feedback via Platypus output - -## Testing Recommendations - -### For Installer - -1. **Test Docker Detection:** - - With Docker Desktop running → Should proceed - - With Docker Desktop installed but not running → Should show "Start Docker Desktop" dialog - - With Docker Desktop not installed → Should show "Install Docker Desktop" button - -2. **Verify Logging:** - - Check Platypus progress window shows PATH and detection results - - Confirm helpful messages guide user - -3. **Test Icon:** - - Check app icon appears in Finder - - Check app icon appears in Dock when running - - Note: Dialogs won't show icon (macOS limitation) - -### For Uninstaller - -1. **Test Container Removal:** - - Start MeticAI installation with containers running - - Run uninstaller - - Verify with: `docker ps -a | grep -E "(meticai|meticai-server|gemini-client|meticulous-mcp)"` - - Should return no results - -2. **Test Multiple Scenarios:** - - With docker-compose.yml present - - With docker-compose.yml deleted - - With containers stopped - - With containers running - - All should successfully remove containers - -3. **Check Logging:** - - Verify detailed logs show what was removed - - Confirm progress messages are clear - -## Build Instructions - -To rebuild apps with fixes: - -```bash -cd macos-installer - -# Build installer -./build-macos-app.sh - -# Build uninstaller -./build-uninstaller-app.sh - -# Both apps will be in macos-installer/build/ -``` - -## Expected Behavior After Fixes - -### Installer -✅ Detects Docker Desktop correctly (both command and daemon) -✅ Shows specific messages for each prerequisite state -✅ Provides clickable buttons to install missing tools -✅ App icon visible in Finder/Dock -✅ Logs help diagnose any issues - -### Uninstaller -✅ Removes ALL Docker containers (running and stopped) -✅ Works even without docker-compose.yml -✅ Handles various container naming conventions -✅ App icon visible in Finder/Dock -✅ Detailed logging shows what was removed - -## Technical Notes - -### PATH in Platypus Apps - -Platypus apps have a minimal PATH by default. Always explicitly set PATH at script start: -```bash -export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Docker.app/Contents/Resources/bin:$PATH" -``` - -### Docker Container Cleanup - -Always use multiple strategies: -1. `docker compose down --volumes --remove-orphans` -2. Search with `docker ps -aq` (not just `docker ps -q`) -3. Filter by multiple criteria (name, label) -4. Use `docker rm -f` to force removal - -### macOS App Icons - -- Info.plist controls Finder/Dock icons -- AppleScript dialogs don't inherit app icons -- Use `CFBundleIconFile` pointing to `AppIcon.icns` -- Include both `CFBundleName` and `CFBundleDisplayName` - -## Commit Details - -**Files Modified:** 4 -- macos-installer/install-wrapper.sh (PATH + logging) -- macos-installer/uninstall-wrapper.sh (PATH + enhanced removal) -- macos-installer/build-macos-app.sh (Info.plist fixes) -- macos-installer/build-uninstaller-app.sh (Info.plist fixes) - -**Lines Changed:** ~80 additions, ~15 modifications - -**Backward Compatibility:** ✅ Fully compatible with previous installs diff --git a/macos-installer/ENHANCEMENTS_SUMMARY.md b/macos-installer/ENHANCEMENTS_SUMMARY.md deleted file mode 100644 index 5efedc2..0000000 --- a/macos-installer/ENHANCEMENTS_SUMMARY.md +++ /dev/null @@ -1,224 +0,0 @@ -# macOS Apps Enhancement Summary - -## Overview - -This document summarizes the enhancements made to the macOS installer and the creation of a new uninstaller app based on user feedback. - -## User Feedback Addressed - -From @hessius comment #3772920100: -1. Question about MeticAI branding in UI/app icon -2. Request to add clickable link to Google API page -3. Request to create similar app for uninstaller - -## Changes Made (Commit 489364b) - -### 1. Branding Confirmation ✅ - -**Answer:** YES, both apps use MeticAI branding! - -**Details:** -- Both build scripts reference `resources/MeticAI.icns` -- App icon shows MeticAI logo in: - - Finder - - Dock - - App window - - System dialogs -- Professional branded appearance - -**Build Script Configuration:** -```bash -ICON_FILE="$REPO_ROOT/resources/MeticAI.icns" -``` - -### 2. Clickable API Link ✅ - -**Enhancement:** API key dialog now has a "Get API Key" button - -**Before:** -``` -Dialog message: "Get your free API key at: https://aistudio.google.com/app/apikey" -Buttons: [Cancel] [Continue] -``` - -**After:** -``` -Dialog message: "Click 'Get API Key' to open the Google AI Studio page in your browser." -Buttons: [Cancel] [Get API Key] [Continue] -``` - -**User Flow:** -1. User sees API key dialog -2. Clicks "Get API Key" button -3. Browser opens to https://aistudio.google.com/app/apikey -4. User creates/copies API key -5. Dialog reappears -6. User pastes key and clicks "Continue" - -**Implementation:** -- Modified `get_api_key()` function in `install-wrapper.sh` -- Uses AppleScript to capture button press -- `open` command launches browser -- Loop returns to dialog after button press -- Validates non-empty before accepting - -### 3. Uninstaller App ✅ - -**New App:** MeticAI Uninstaller - complete GUI-based uninstaller - -**Files Created:** -1. `macos-installer/uninstall-wrapper.sh` (280 lines) - - Fully GUI uninstallation wrapper - - No Terminal window - - Background execution - -2. `macos-installer/build-uninstaller-app.sh` (180 lines) - - Builds uninstaller .app bundle - - Platypus or manual build support - - Uses MeticAI icon - -3. `tests/test_macos_uninstaller.bats` (29 tests) - - Comprehensive test coverage - - All tests passing ✅ - -**Features:** -- ✅ Confirmation dialog before uninstalling -- ✅ Auto-detects installation location -- ✅ Searches common locations: - - `~/MeticAI` - - `~/Documents/MeticAI` - - `/Applications/MeticAI` - - Current directory -- ✅ Asks user to locate if not found -- ✅ Background uninstallation (no Terminal) -- ✅ Removes all components: - - Docker containers (`docker compose down`) - - Docker images (meticai, meticai-server, gemini-client, meticulous-mcp) - - Repositories (meticulous-source, meticai-web) - - Configuration files (.env, .versions.json, .rebuild-needed) - - macOS integrations: - - Dock shortcuts (`~/Applications/MeticAI.app`) - - LaunchAgents (rebuild watcher) -- ✅ Optional directory removal - - Asks before deleting installation folder - - User can choose to keep or remove -- ✅ Success/error dialogs with clear feedback -- ✅ Progress updates via Platypus progress bar -- ✅ Uses MeticAI branding (icon) - -**Build & Usage:** -```bash -# Build the uninstaller -cd macos-installer -./build-uninstaller-app.sh - -# Test it -open "build/MeticAI Uninstaller.app" - -# Create DMG for distribution -hdiutil create -volname "MeticAI Uninstaller" \ - -srcfolder "build/MeticAI Uninstaller.app" \ - -ov -format UDZO "build/MeticAI-Uninstaller.dmg" -``` - -**Uninstallation Flow:** -``` -Launch app - → Confirmation dialog - → Find installation directory - → Auto-detect common locations - → OR ask user to locate - → Background uninstallation: - → Stop containers - → Remove images - → Delete repositories - → Delete config files - → Remove macOS integrations - → Ask about directory removal - → Success dialog -``` - -## Test Coverage - -### Installer Tests (43 tests) -```bash -$ bats tests/test_macos_installer.bats -1..43 -ok 1-43 (all passing) -``` - -### Uninstaller Tests (29 tests) -```bash -$ bats tests/test_macos_uninstaller.bats -1..29 -ok 1-29 (all passing) -``` - -**Total: 72 tests, all passing ✅** - -### CI Integration - -Updated `.github/workflows/tests.yml`: -- Added uninstaller tests -- Added syntax validation for uninstaller scripts -- All tests run automatically on PR - -## Documentation Updates - -### Updated Files -1. `macos-installer/README.md` - - Added uninstaller section - - Updated branding information - - Added build instructions for both apps - - Updated distribution section - -2. `.github/workflows/tests.yml` - - Added uninstaller test execution - - Added uninstaller syntax validation - -## Distribution - -Both apps can now be distributed: - -**Installer DMG:** -```bash -hdiutil create -volname "MeticAI Installer" \ - -srcfolder "build/MeticAI Installer.app" \ - -ov -format UDZO "build/MeticAI-Installer.dmg" -``` - -**Uninstaller DMG:** -```bash -hdiutil create -volname "MeticAI Uninstaller" \ - -srcfolder "build/MeticAI Uninstaller.app" \ - -ov -format UDZO "build/MeticAI-Uninstaller.dmg" -``` - -## Summary - -All three items from user feedback have been successfully addressed: - -| Item | Status | Details | -|------|--------|---------| -| 1. Branding | ✅ Complete | Both apps use MeticAI.icns icon | -| 2. Clickable API link | ✅ Complete | "Get API Key" button opens browser | -| 3. Uninstaller app | ✅ Complete | Full GUI uninstaller created | - -**Additional Benefits:** -- Consistent user experience across install/uninstall -- Professional branded appearance -- Better user experience (clickable links) -- Complete GUI - no Terminal windows -- Comprehensive test coverage (72 tests) -- CI integration -- Ready for distribution - -**Status: Ready for production use!** 🎉 - ---- - -**Commit:** 489364b -**Date:** January 20, 2026 -**Files Changed:** 6 files -**Lines Added:** 751 -**Lines Removed:** 25 diff --git a/macos-installer/FULLY_GUI_SUMMARY.md b/macos-installer/FULLY_GUI_SUMMARY.md deleted file mode 100644 index 2bc5a7b..0000000 --- a/macos-installer/FULLY_GUI_SUMMARY.md +++ /dev/null @@ -1,262 +0,0 @@ -# Fully GUI Installer - Implementation Summary - -## What Changed - -In response to @hessius feedback: *"Great start but for this installation flow I don't want the terminal to be exposed to the end user. Ie completely GUI + background"* - -The installer has been completely redesigned to be **100% GUI-based** with **NO Terminal window**. - -## Before vs After - -### Before (Hybrid Approach) -``` -┌─────────────────────────────────────┐ -│ MeticAI Installer.app │ -│ │ -│ 1. GUI Dialog: Welcome │ -│ 2. GUI Dialog: Prerequisites │ -│ 3. GUI Dialog: Location │ -│ │ -│ 4. ⚠️ OPENS TERMINAL WINDOW │ -│ ┌──────────────────────────┐ │ -│ │ $ Enter API key: │ │ -│ │ $ Enter Meticulous IP: │ │ -│ │ $ Enter Server IP: │ │ -│ │ $ Installing... │ │ -│ │ $ [lots of output] │ │ -│ └──────────────────────────┘ │ -│ │ -└─────────────────────────────────────┘ -``` - -**Issues:** -- ❌ Terminal window visible to user -- ❌ Command line output exposed -- ❌ User must interact with Terminal -- ❌ Not truly "GUI-only" - -### After (Fully GUI Approach) -``` -┌─────────────────────────────────────┐ -│ MeticAI Installer.app │ -│ │ -│ 1. GUI Dialog: Welcome │ -│ 2. GUI Dialog: Prerequisites │ -│ 3. GUI Dialog: Location │ -│ 4. GUI Dialog: Enter API Key │ -│ 5. GUI Dialog: Enter Meticulous IP │ -│ 6. GUI Dialog: Enter/Detect IP │ -│ │ -│ 7. Background Installation │ -│ ┌──────────────────────────┐ │ -│ │ [Running silently...] │ │ -│ │ Progress Bar: ████░░░░ │ │ -│ └──────────────────────────┘ │ -│ │ -│ 8. GUI Dialog: Success! 🎉 │ -│ 9. Auto-opens web interface │ -│ │ -└─────────────────────────────────────┘ -``` - -**Improvements:** -- ✅ No Terminal window at all -- ✅ All inputs via secure dialogs -- ✅ Installation runs in background -- ✅ Progress via Platypus progress bar -- ✅ Clear success/error dialogs -- ✅ Auto-opens web interface - -## Technical Implementation - -### New Functions Added - -1. **`get_api_key()`** - - Secure text input dialog - - Validates non-empty - - Shows error if empty - -2. **`get_meticulous_ip()`** - - IP address input dialog - - Validates non-empty - - Clear placeholder example - -3. **`get_server_ip()`** - - Auto-detects local IP - - Offers to use detected IP - - Falls back to manual input - -4. **`run_installation()`** - - Clones repository directly - - Creates .env configuration - - Clones dependencies - - Builds Docker containers - - All in background, no Terminal - -5. **`show_progress()`** - - Outputs progress messages - - Captured by Platypus progress bar - - Non-blocking feedback - -### Installation Flow - -``` -┌─────────────────────────────────────────────┐ -│ 1. Launch App │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 2. Show Welcome Dialog │ -│ "Welcome to MeticAI Installer..." │ -│ [Cancel] [OK] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 3. Check Prerequisites │ -│ ├─ Git installed? ────> If No: Show help│ -│ └─ Docker installed? ─> If No: Show help│ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 4. Get Installation Location │ -│ "Where to install?" │ -│ Default: ~/MeticAI │ -│ [Cancel] [Choose Folder] [Use Default] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 5. Get Google Gemini API Key │ -│ "Enter your API key:" │ -│ [____________________________] │ -│ [Cancel] [Continue] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 6. Get Meticulous Machine IP │ -│ "Enter Meticulous IP:" │ -│ Example: 192.168.1.100 │ -│ [____________________________] │ -│ [Cancel] [Continue] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 7. Get Server IP (with auto-detect) │ -│ "Detected: 192.168.1.50" │ -│ "Use this IP?" │ -│ [Use Different] [Use This] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 8. Show Starting Dialog │ -│ "Installation will run in background..." │ -│ [OK] (auto-dismisses after 10 seconds) │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 9. Background Installation │ -│ ┌────────────────────────────────────┐ │ -│ │ PROGRESS: Downloading installer... │ │ -│ │ PROGRESS: Cloning repository... │ │ -│ │ PROGRESS: Creating configuration...│ │ -│ │ PROGRESS: Setting up dependencies..│ │ -│ │ PROGRESS: Building containers... │ │ -│ │ PROGRESS: Installation complete! │ │ -│ └────────────────────────────────────┘ │ -│ (Platypus shows progress bar) │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 10. Success Dialog │ -│ "Installation Complete! ✓" │ -│ "Web Interface: http://192.168.1.50:3550│ -│ "Opening in browser..." │ -│ [OK] │ -└──────────────┬──────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ 11. Auto-Open Web Interface │ -│ (Browser launches automatically) │ -└─────────────────────────────────────────────┘ -``` - -## Code Changes - -### install-wrapper.sh -- **Lines 1-30**: Updated header, added `show_progress()` function -- **Lines 99-225**: Added new GUI input collection functions -- **Lines 227-320**: Rewrote `main()` to collect all inputs before installation -- **Lines 322-410**: New `run_installation()` for background execution - -### Tests Updated -- Removed: Test for Terminal window opening -- Removed: Test for METICAI_INSTALL_METHOD variable -- Added: Test for API key dialog -- Added: Test for Meticulous IP dialog -- Added: Test for Server IP dialog -- Added: Test confirming NO Terminal window - -### Documentation Updated -- **README.md**: "100% GUI - No Terminal window!" -- **macos-installer/README.md**: Updated flow, removed hybrid approach -- **macos-installer/QUICKSTART.md**: Updated user instructions - -## Test Results - -```bash -$ bats tests/test_macos_installer.bats -1..43 -ok 1 install-wrapper.sh exists and is readable -ok 2 install-wrapper.sh has correct shebang -ok 3 install-wrapper.sh is executable -ok 4 install-wrapper.sh has valid bash syntax -ok 5 install-wrapper.sh contains welcome dialog function -ok 6 install-wrapper.sh contains prerequisite check function -ok 7 install-wrapper.sh checks for git -ok 8 install-wrapper.sh checks for docker -ok 9 install-wrapper.sh uses osascript for dialogs -ok 10 install-wrapper.sh clones repository directly (not via web_install.sh) -ok 11 install-wrapper.sh collects API key via dialog -ok 12 install-wrapper.sh collects Meticulous IP via dialog -ok 13 install-wrapper.sh collects Server IP via dialog -ok 14 install-wrapper.sh has logging functions -ok 15 install-wrapper.sh runs installation in background (no Terminal) -... -ok 43 Info.plist includes required keys -``` - -**All 43 tests passing ✅** - -## Commit - -**Commit Hash**: 1c57815 -**Commit Message**: "Convert to fully GUI installer - no Terminal window exposed to user" - -**Files Changed:** -- macos-installer/install-wrapper.sh (major rewrite) -- tests/test_macos_installer.bats (updated tests) -- README.md (updated installation section) -- macos-installer/README.md (updated documentation) -- macos-installer/QUICKSTART.md (updated user guide) - -## Summary - -The macOS installer is now **completely GUI-based** with: -- ✅ All inputs via AppleScript dialogs -- ✅ Background installation (no Terminal) -- ✅ Progress feedback via Platypus -- ✅ Auto-opens web interface -- ✅ Better error handling -- ✅ Maintains security -- ✅ All tests passing - -**Status: Complete and ready for use!** 🎉 diff --git a/macos-installer/FUTURE_ENHANCEMENTS.md b/macos-installer/FUTURE_ENHANCEMENTS.md deleted file mode 100644 index e68a2e9..0000000 --- a/macos-installer/FUTURE_ENHANCEMENTS.md +++ /dev/null @@ -1,285 +0,0 @@ -# Future Enhancements for macOS Installer - -This document tracks potential improvements and enhancements for the macOS installer app. - -## Security Enhancements - -### 1. Checksum Verification of Downloaded Installer -**Priority: High** -**Status: Planned** - -Currently, the installer downloads `web_install.sh` from the main branch without verification. - -**Proposed Solution:** -- Option A: Download from a specific commit hash instead of 'main' branch -- Option B: Implement SHA-256 checksum verification -- Option C: Use GPG signature verification - -**Implementation:** -```bash -# Example with checksum verification -EXPECTED_CHECKSUM="abc123..." -DOWNLOADED_CHECKSUM=$(shasum -a 256 "$TEMP_INSTALLER" | awk '{print $1}') - -if [ "$EXPECTED_CHECKSUM" != "$DOWNLOADED_CHECKSUM" ]; then - echo "ERROR: Installer checksum mismatch - potential tampering detected" - exit 1 -fi -``` - -**Trade-offs:** -- Commit hash: Ensures exact version but requires updating installer for each release -- Checksum: Good security but requires maintaining checksum file -- GPG signature: Best security but adds complexity - -### 2. Code Signing and Notarization -**Priority: Medium** -**Status: Documented in README** - -For wider distribution, the app should be code-signed and notarized by Apple. - -**Benefits:** -- No "app is damaged" warnings -- Better security reputation -- Easier distribution - -**Requirements:** -- Apple Developer account ($99/year) -- Developer ID certificate -- Notarization workflow - -**See:** README.md for detailed instructions - -## User Experience Enhancements - -### 3. Pure GUI Mode (No Terminal) -**Priority: Medium** -**Status: Proposed** - -Currently, the installer opens Terminal for the main installation. Could collect all inputs via GUI. - -**Proposed Changes:** -- API key input via secure text field dialog -- IP address input via text field with validation -- Progress bar showing installation steps -- Log viewer for detailed output (optional) - -**Implementation Considerations:** -- AppleScript has limited UI capabilities -- May need to use Swift/Objective-C for better UI -- Trade-off: Less transparency vs more user-friendly - -### 4. Auto-Install Prerequisites -**Priority: Low** -**Status: Proposed** - -Currently, if Git or Docker are missing, we show instructions. Could auto-install with user permission. - -**Proposed Changes:** -- Offer to install Homebrew if missing -- Auto-install Git via `xcode-select --install` -- Auto-download Docker Desktop installer -- Show progress during installation - -**Challenges:** -- Requires sudo permissions -- Docker Desktop needs manual setup after install -- May take significant time - -### 5. Installation Progress Indicator -**Priority: Medium** -**Status: Proposed** - -Show a native macOS progress bar instead of just Terminal output. - -**Options:** -- Use AppleScript progress indicator -- Use Swift app with progress bar -- Show estimated time remaining -- Display current step - -### 6. Offline Installer Option -**Priority: Low** -**Status: Proposed** - -Bundle all dependencies in the installer for offline installation. - -**What to Bundle:** -- web_install.sh script -- Docker images (large - ~1GB+) -- Git installer -- All documentation - -**Trade-offs:** -- Much larger download size -- Easier for users with limited connectivity -- More complex build process - -## Internationalization - -### 7. Multi-Language Support -**Priority: Low** -**Status: Proposed** - -Support multiple languages for dialogs and messages. - -**Languages to Consider:** -- Spanish -- French -- German -- Japanese - -**Implementation:** -- Detect system language -- Load appropriate string resources -- Fallback to English if translation unavailable - -## Advanced Features - -### 8. Update Checker App -**Priority: Medium** -**Status: Proposed** - -Separate app to check for and apply MeticAI updates. - -**Features:** -- Check for updates with one click -- Download and apply updates -- Show changelog -- Rollback option - -### 9. Uninstaller App -**Priority: Low** -**Status: Proposed** - -GUI version of the uninstall.sh script. - -**Features:** -- List installed components -- Choose what to remove -- Confirmation dialogs -- Progress indication - -### 10. Configuration Manager App -**Priority: Low** -**Status: Proposed** - -GUI for managing MeticAI configuration. - -**Features:** -- Edit API key -- Update IP addresses -- Restart services -- View logs -- Test connection - -## Developer Experience - -### 11. Automated DMG Creation in CI -**Priority: High** -**Status: Planned** - -Create GitHub Actions workflow to build and publish DMG on releases. - -**Workflow:** -```yaml -name: Build macOS Installer -on: - release: - types: [created] -jobs: - build-dmg: - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - name: Install Platypus - run: brew install platypus - - name: Build app - run: cd macos-installer && ./build-macos-app.sh - - name: Create DMG - run: hdiutil create ... - - name: Upload to release - uses: actions/upload-release-asset@v1 -``` - -### 12. Automated Testing on macOS -**Priority: Medium** -**Status: Planned** - -Currently tests run on Linux. Should also test on macOS. - -**Implementation:** -- Add macOS runner to CI -- Test app launch (may need headless mode) -- Test dialog display -- Test installer execution - -## Documentation Improvements - -### 13. Video Tutorial -**Priority: Low** -**Status: Proposed** - -Create a video showing installation process. - -**Content:** -- Download DMG -- Open and install -- Launch app -- Follow prompts -- Access web interface - -### 14. Troubleshooting Database -**Priority: Low** -**Status: Proposed** - -Maintain database of common issues and solutions. - -**Examples:** -- "App won't open" → Quarantine removal -- "Docker not found" → Installation instructions -- "Installation fails" → Check logs - -## Implementation Priority - -### High Priority (Next Release) -1. Checksum verification -2. Automated DMG creation in CI - -### Medium Priority (Future Release) -3. Pure GUI mode -4. Installation progress indicator -5. Update checker app -6. Automated macOS testing - -### Low Priority (Backlog) -7. Auto-install prerequisites -8. Offline installer -9. Multi-language support -10. Uninstaller app -11. Configuration manager -12. Video tutorial -13. Troubleshooting database - -## Contributing - -Want to implement one of these enhancements? Great! - -1. Check if there's an existing issue or PR -2. Create an issue to discuss the approach -3. Implement and test thoroughly -4. Submit a PR with comprehensive tests -5. Update this document to mark as "In Progress" or "Complete" - -## Notes - -- All enhancements should maintain backward compatibility -- Test coverage must remain at 100% -- Documentation must be updated -- Security enhancements take priority over features -- User experience improvements should not sacrifice transparency - ---- - -**Last Updated:** January 19, 2026 -**Status:** Active planning document diff --git a/macos-installer/IMPLEMENTATION_SUMMARY.md b/macos-installer/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 2338c42..0000000 --- a/macos-installer/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,317 +0,0 @@ -# macOS Installer Implementation Summary - -## Overview - -This document summarizes the implementation of the macOS installer app for MeticAI. - -## What Was Implemented - -A complete macOS installer application that wraps the existing `web_install.sh` script with a graphical user interface, making MeticAI installation accessible to users who prefer not to use the command line. - -## Key Features - -### For End Users -- ✅ **GUI-based installation** - No terminal commands required to start -- ✅ **Prerequisite checking** - Automatically detects missing Git or Docker -- ✅ **Visual prompts** - AppleScript dialogs for all user choices -- ✅ **Installation location picker** - Choose where to install with folder browser -- ✅ **Clear instructions** - Helpful error messages and links to download prerequisites -- ✅ **Progress visibility** - Terminal shows real-time installation progress -- ✅ **Secure input** - API keys entered directly in Terminal (not stored in dialogs) - -### For Developers -- ✅ **Two build methods** - Platypus (recommended) or manual bundle creation -- ✅ **Professional app bundle** - Proper Info.plist, icon, and structure -- ✅ **DMG distribution** - Instructions for creating distributable installers -- ✅ **Code signing support** - Optional notarization for wider distribution -- ✅ **Comprehensive documentation** - Multiple guides for different use cases -- ✅ **Full test coverage** - 41 BATS tests covering all functionality -- ✅ **CI integration** - Automated testing in GitHub Actions - -## Files Created - -### Scripts -1. **`macos-installer/install-wrapper.sh`** (247 lines) - - Main wrapper script with AppleScript dialogs - - Prerequisite checking (Git, Docker) - - Installation location selection - - Downloads and executes web_install.sh in Terminal - -2. **`macos-installer/build-macos-app.sh`** (160 lines) - - Builds .app bundle using Platypus or manually - - Configures app metadata and icon - - Creates proper Info.plist - - Instructions for DMG creation - -### Documentation -3. **`macos-installer/README.md`** (345 lines) - - Complete technical documentation - - Build instructions (Platypus and manual) - - Distribution guide (DMG, ZIP, code signing) - - Architecture explanation - - Troubleshooting guide - - Future enhancements roadmap - -4. **`macos-installer/QUICKSTART.md`** (215 lines) - - Quick reference for users - - Quick reference for developers - - FAQ section - - Common issues and solutions - -### Tests -5. **`tests/test_macos_installer.bats`** (238 lines) - - 41 comprehensive tests - - Script validation (syntax, permissions, structure) - - Function presence checks - - Configuration validation - - Integration testing - - All tests passing ✅ - -### Configuration Updates -6. **`.github/workflows/tests.yml`** (updated) - - Added macOS installer tests to CI - - Added syntax validation for installer scripts - - Added shellcheck linting for installer scripts - -7. **`README.md`** (updated) - - Added macOS installer as "Option 1" in installation section - - Clear instructions for non-technical users - - Link to detailed documentation - -8. **`.gitignore`** (updated) - - Exclude build artifacts (*.app, *.dmg) - - Exclude build directory - -## Technical Design - -### Hybrid Approach - -The installer uses a **hybrid GUI/Terminal approach**: - -``` -GUI Dialogs (AppleScript) Terminal Window -───────────────────────── ───────────────────── -• Welcome message • Real-time progress -• Prerequisite checks • Interactive configuration -• Location selection • API key input -• Error messages • IP address setup -• Help/instructions • QR code display - • Completion message -``` - -**Why this design?** -- GUI for simple choices = user-friendly -- Terminal for installation = transparent, shows progress -- Terminal for sensitive input = secure, not stored in memory -- Hybrid = best of both worlds - -### Installation Flow - -``` -User launches "MeticAI Installer.app" - ↓ -Show welcome dialog (AppleScript) - ↓ -Check for Git and Docker - ↓ (if missing) -Show installation instructions + download links - ↓ (if present) -Ask for installation location - ↓ -Download web_install.sh from GitHub - ↓ -Create temporary script - ↓ -Open Terminal with installation script - ↓ -User sees progress and provides configuration: - - Google Gemini API key - - Meticulous machine IP - - Server IP - ↓ -Installation completes - ↓ -Show QR code and success message -``` - -### Build Process - -``` -Developer runs: ./build-macos-app.sh - ↓ -Check for Platypus - ├─ (if available) Use Platypus CLI - └─ (if not) Create .app bundle manually - ↓ -Copy install-wrapper.sh to Contents/MacOS/ - ↓ -Copy MeticAI.icns to Contents/Resources/ - ↓ -Generate Info.plist with metadata - ↓ -Set executable permissions - ↓ -Output: build/MeticAI Installer.app - ↓ -(Optional) Create DMG for distribution -``` - -## Test Coverage - -### Test Categories (41 tests total) - -1. **Script Validation** (13 tests) - - File existence and permissions - - Shebang correctness - - Bash syntax validation - - Function presence - -2. **Build Script Validation** (12 tests) - - Configuration correctness - - Platypus integration - - Manual fallback - - Info.plist generation - -3. **Documentation Validation** (6 tests) - - README completeness - - Installation instructions - - Troubleshooting content - - Code signing information - -4. **Integration Tests** (10 tests) - - Proper app structure - - Correct URL usage - - Environment variable handling - - Security best practices - -### Test Results -```bash -$ bats tests/test_macos_installer.bats -1..41 -ok 1 install-wrapper.sh exists and is readable -ok 2 install-wrapper.sh has correct shebang -... -ok 41 Info.plist includes required keys -``` - -**All 41 tests passing ✅** - -## CI Integration - -The GitHub Actions workflow now: -1. Runs all 41 macOS installer BATS tests -2. Validates bash syntax of both scripts -3. Lints scripts with shellcheck -4. Fails if any test fails - -This ensures code quality and prevents regressions. - -## Distribution Options - -### Option 1: DMG File (Recommended) -```bash -hdiutil create -volname "MeticAI Installer" \ - -srcfolder "build/MeticAI Installer.app" \ - -ov -format UDZO \ - "build/MeticAI-Installer.dmg" -``` - -Users download DMG, drag to Applications, launch. - -### Option 2: ZIP Archive -```bash -zip -r "MeticAI-Installer.zip" "build/MeticAI Installer.app" -``` - -Users download ZIP, extract, move to Applications, launch. - -### Option 3: Direct .app (Development) -Share the .app bundle directly for testing. - -## Security Considerations - -### Current Implementation -- ✅ Downloads installer from official GitHub repository -- ✅ Uses HTTPS for all downloads -- ✅ Sensitive data (API keys) entered in Terminal, not stored -- ✅ Prerequisite checks prevent execution without required tools -- ✅ Clear error messages guide users to official sources - -### Optional Enhancements -- Code signing with Developer ID certificate -- Notarization with Apple -- Gatekeeper compatibility -- See README.md for instructions - -## Usage Statistics - -### Lines of Code -- Shell scripts: ~407 lines -- Documentation: ~560 lines -- Tests: 238 lines -- **Total: ~1,205 lines** - -### Files Modified/Created -- Created: 5 new files -- Updated: 3 existing files -- **Total: 8 files changed** - -## Acceptance Criteria - COMPLETE ✅ - -✅ **Users can launch the installer** - Double-click .app bundle - -✅ **Follow graphical prompts** - AppleScript dialogs guide users - -✅ **Complete setup without Terminal** - Initial launch via GUI, then Terminal shows progress - -✅ **All critical output presented** - Terminal displays all installation steps - -✅ **All input collected** - API key, IP addresses via interactive prompts - -✅ **Appropriate error handling** - Missing prerequisites detected with helpful messages - -✅ **Success confirmation** - QR code and completion message shown - -## Future Enhancements - -Potential improvements (documented in README.md): - -1. **Pure GUI mode** - Collect all inputs via dialogs (no Terminal) -2. **Native progress bar** - macOS-native progress indicator -3. **Silent prerequisite install** - Auto-install Git, Docker if user approves -4. **Offline installer** - Bundle all dependencies -5. **Uninstaller app** - GUI for uninstallation -6. **Update checker app** - GUI for checking/applying updates -7. **Multi-language support** - Internationalization - -## Documentation - -### For Users -- Main README.md: Overview and quick start -- macos-installer/QUICKSTART.md: Quick reference guide -- Built-in dialogs: Step-by-step guidance - -### For Developers -- macos-installer/README.md: Complete technical documentation -- macos-installer/QUICKSTART.md: Build quick reference -- Inline comments: Script documentation - -## Conclusion - -The macOS installer app provides a professional, user-friendly installation experience for MeticAI that: - -- **Lowers the barrier to entry** for non-technical users -- **Maintains transparency** by showing installation progress -- **Follows macOS conventions** with proper app bundle structure -- **Is fully tested** with comprehensive BATS test suite -- **Is well documented** with multiple guides -- **Is CI integrated** for quality assurance -- **Is extensible** with clear path for future enhancements - -The implementation is **complete**, **tested**, and **ready for use**. - ---- - -**Created**: January 19, 2026 -**Version**: 1.0.0 -**Status**: Complete ✅ diff --git a/macos-installer/QUICKSTART.md b/macos-installer/QUICKSTART.md deleted file mode 100644 index f683ddc..0000000 --- a/macos-installer/QUICKSTART.md +++ /dev/null @@ -1,218 +0,0 @@ -# Quick Start Guide for macOS Installer - -This is a quick reference for building and using the MeticAI macOS installer app. - -## For Users - -### Downloading and Installing - -1. **Download** the installer from the [releases page](https://github.com/hessius/MeticAI/releases) *(coming soon)* - -2. **Open** the DMG file and **drag** "MeticAI Installer.app" to your Applications folder - -3. **Launch** the app from Applications - -4. **Follow** the fully graphical installation: - - Click OK on the welcome screen - - If prompted, install Git and Docker Desktop (links provided) - - Choose where to install MeticAI (default: ~/MeticAI) - - Enter your Google Gemini API key in the dialog - - Enter your Meticulous machine's IP address - - Confirm or enter your server's IP address - - Wait while installation runs in the background - - **NO Terminal window!** Everything is GUI-based - -5. **Access** MeticAI - the web interface opens automatically when complete! - -### Troubleshooting - -**"App is damaged" or "Can't be opened"** - -This happens because the app isn't code-signed. Fix it by: - -```bash -# Remove quarantine attribute -xattr -cr "/Applications/MeticAI Installer.app" -``` - -Or: -1. Right-click the app -2. Select "Open" -3. Click "Open" in the security dialog - -**Prerequisites missing** - -The installer will detect missing tools and show installation instructions: -- **Docker Desktop**: Download from https://www.docker.com/products/docker-desktop -- **Git**: Run `xcode-select --install` in Terminal - -## For Developers - -### Building the Installer - -#### Prerequisites - -```bash -# Option 1: Install Platypus (recommended) -brew install platypus - -# Option 2: Use manual bundle creation (no additional tools needed) -``` - -#### Build Process - -```bash -# 1. Clone the repository -git clone https://github.com/hessius/MeticAI.git -cd MeticAI/macos-installer - -# 2. Run the build script -./build-macos-app.sh - -# 3. Test the app -open "build/MeticAI Installer.app" - -# 4. (Optional) Create a DMG for distribution -hdiutil create -volname "MeticAI Installer" \ - -srcfolder "build/MeticAI Installer.app" \ - -ov -format UDZO \ - "build/MeticAI-Installer.dmg" -``` - -### Directory Structure - -``` -macos-installer/ -├── install-wrapper.sh # GUI wrapper for web_install.sh -├── build-macos-app.sh # Build script to create .app -├── README.md # Detailed documentation -└── build/ # Output directory (created by build script) - ├── MeticAI Installer.app - └── MeticAI-Installer.dmg -``` - -### Testing - -```bash -# Run BATS tests -cd .. -bats tests/test_macos_installer.bats - -# All 41 tests should pass -``` - -### Distribution - -**Creating a Release:** - -1. Build the app using `build-macos-app.sh` -2. Create a DMG using the `hdiutil` command shown above -3. Upload the DMG to GitHub releases -4. Users download the DMG and install - -**Optional Code Signing:** - -For wider distribution, code sign and notarize: - -```bash -# Code sign -codesign --deep --force --verify --verbose \ - --sign "Developer ID Application: Your Name (TEAM_ID)" \ - "build/MeticAI Installer.app" - -# Create ZIP for notarization -ditto -c -k --keepParent "build/MeticAI Installer.app" "build/MeticAI-Installer.zip" - -# Submit for notarization -xcrun notarytool submit "build/MeticAI-Installer.zip" \ - --apple-id "your@email.com" \ - --team-id "TEAM_ID" \ - --password "app-specific-password" - -# Staple ticket -xcrun stapler staple "build/MeticAI Installer.app" -``` - -## How It Works - -### Installation Flow - -``` -User launches app - ↓ -Welcome dialog (AppleScript) - ↓ -Check prerequisites (Git, Docker) - ↓ (if missing) -Show installation instructions + links - ↓ (if all present) -Choose installation location - ↓ -Download web_install.sh from GitHub - ↓ -Open Terminal with installer - ↓ -User provides configuration - ↓ -Installation completes - ↓ -QR code and success message -``` - -### Components - -1. **install-wrapper.sh**: Uses AppleScript (`osascript`) to show dialogs and check prerequisites -2. **build-macos-app.sh**: Creates .app bundle with proper structure and metadata -3. **Terminal**: Handles the actual installation for transparency and interactive input - -### Why This Design? - -- **GUI dialogs** for simple choices = user-friendly -- **Terminal** for installation = transparent, shows progress, handles interactive input -- **Hybrid approach** = best of both worlds - -## FAQ - -**Q: Why does it open Terminal?** - -A: The Terminal shows installation progress in real-time and allows secure input of sensitive data like API keys. This transparency helps users understand what's happening. - -**Q: Can I skip the GUI entirely?** - -A: Yes! Use the one-line installer instead: -```bash -curl -fsSL https://raw.githubusercontent.com/hessius/MeticAI/main/web_install.sh | bash -``` - -**Q: Does this work on Windows or Linux?** - -A: No, this is macOS-specific. On other platforms, use the web installer or local-install.sh script directly. - -**Q: How do I update MeticAI after installation?** - -A: From the installation directory, run: -```bash -./update.sh -``` - -**Q: How do I uninstall?** - -A: From the installation directory, run: -```bash -./uninstall.sh -``` - -## Additional Resources - -- [Full macOS Installer Documentation](README.md) -- [Main MeticAI Documentation](../README.md) -- [Technical Details](../TECHNICAL.md) -- [API Documentation](../API.md) - ---- - -**Need Help?** - -- 📖 [Read the full documentation](README.md) -- 🐛 [Report an issue](https://github.com/hessius/MeticAI/issues) -- 💬 [Ask a question](https://github.com/hessius/MeticAI/discussions) diff --git a/macos-installer/README.md b/macos-installer/README.md deleted file mode 100644 index e73982d..0000000 --- a/macos-installer/README.md +++ /dev/null @@ -1,398 +0,0 @@ -# MeticAI macOS Apps - -This directory contains the scripts and resources needed to build standalone macOS applications for MeticAI. Two apps are available: - -1. **MeticAI Installer** - GUI-based installation app -2. **MeticAI Uninstaller** - GUI-based uninstallation app - -Both apps provide a **fully graphical user interface** with **NO Terminal window** - everything runs in the background with GUI dialogs for input and progress feedback. - -## MeticAI Installer - -The installer app provides a completely GUI-based installation experience: - -- ✅ **Welcome dialog** explaining the installation process -- ✅ **Prerequisite checking** for Git and Docker with helpful installation instructions -- ✅ **Installation location picker** via dialog or folder browser -- ✅ **API key input** via secure dialog with clickable link to get API key -- ✅ **IP address configuration** via dialogs with auto-detection -- ✅ **Background installation** - no Terminal window shown to user -- ✅ **Progress feedback** via GUI dialogs -- ✅ **Success/error dialogs** with clear next steps -- ✅ **Auto-opens web interface** when installation completes -- ✅ **Uses MeticAI branding** - app icon shows MeticAI logo - -## MeticAI Uninstaller - -The uninstaller app provides a GUI-based uninstallation experience: - -- ✅ **Confirmation dialog** before uninstalling -- ✅ **Auto-detects installation** location -- ✅ **Background uninstallation** - no Terminal window -- ✅ **Removes all components**: - - Docker containers and images - - Cloned repositories - - Configuration files - - macOS integrations (Dock shortcuts, services) -- ✅ **Optional directory removal** - asks before deleting installation folder -- ✅ **Success/error dialogs** with clear feedback -- ✅ **Uses MeticAI branding** - app icon shows MeticAI logo - -## Building the Apps - -### Prerequisites - -**Option 1: Using Platypus (Recommended)** -```bash -brew install platypus -``` - -**Option 2: Manual Build** -No additional tools required - the build script will create the app bundle manually. - -### Build Instructions - -1. Navigate to the `macos-installer` directory: - ```bash - cd macos-installer - ``` - -2. Make the build script executable: - ```bash - chmod +x build-macos-app.sh - ``` - -3. Run the build script: - ```bash - ./build-macos-app.sh - ``` - -4. The app will be created in `macos-installer/build/MeticAI Installer.app` - -### Testing the App - -Open the app to test it: -```bash -open "build/MeticAI Installer.app" -``` - -The app will: -1. Show a welcome dialog -2. Check for Git and Docker -3. Ask for installation location -4. Collect Google Gemini API key via secure dialog -5. Collect Meticulous machine IP address -6. Collect or auto-detect server IP address -7. Run installation in the background (NO Terminal window) -8. Show progress via dialogs -9. Display success message with web interface URL -10. Auto-open the web interface in your browser - -### Building the Uninstaller - -To build the uninstaller app: - -```bash -cd macos-installer -chmod +x build-uninstaller-app.sh -./build-uninstaller-app.sh -``` - -The app will be created in `macos-installer/build/MeticAI Uninstaller.app` - -Test it: -```bash -open "build/MeticAI Uninstaller.app" -``` - -The uninstaller will: -1. Show a confirmation dialog -2. Auto-detect MeticAI installation (or ask user to locate it) -3. Run uninstallation in the background (NO Terminal window) -4. Remove Docker containers, images, repositories, and config files -5. Ask about removing the installation directory -6. Show success message - -## Distribution - -### Creating DMG files for Distribution - -Create distributable DMG files for both apps: - -**Installer DMG:** -```bash -hdiutil create -volname "MeticAI Installer" \ - -srcfolder "build/MeticAI Installer.app" \ - -ov -format UDZO \ - "build/MeticAI-Installer.dmg" -``` - -**Uninstaller DMG:** -```bash -hdiutil create -volname "MeticAI Uninstaller" \ - -srcfolder "build/MeticAI Uninstaller.app" \ - -ov -format UDZO \ - "build/MeticAI-Uninstaller.dmg" -``` - -Users can then: -1. Download the DMG files -2. Open them and drag apps to Applications -3. Launch the apps from Applications folder - -### Creating ZIP Archives - -Alternatively, create ZIP files: - -```bash -cd build -zip -r "MeticAI-Installer.zip" "MeticAI Installer.app" -zip -r "MeticAI-Uninstaller.zip" "MeticAI Uninstaller.app" -``` - -## How It Works - -### Architecture - -``` -MeticAI Installer.app -├── Contents/ -│ ├── MacOS/ -│ │ └── MeticAI Installer (install-wrapper.sh) -│ ├── Resources/ -│ │ └── AppIcon.icns (MeticAI icon) -│ └── Info.plist (App metadata) -``` - -### Installation Flow - -1. **Welcome Dialog** - User sees introduction and clicks OK to continue -2. **Prerequisites Check** - Verifies Git and Docker are installed - - If missing: Shows instructions and offers to open download links -3. **Location Selection** - User chooses where to install MeticAI - - Default: `~/MeticAI` - - Options: Use default, choose custom folder -4. **Configuration Collection** - All via GUI dialogs: - - Google Gemini API key (secure text input) - - Meticulous machine IP address - - Server IP address (with auto-detection) -5. **Background Installation** - Runs silently in the background - - Clones repository - - Creates configuration files - - Builds and starts Docker containers - - Progress shown via GUI dialogs -6. **Completion** - Success dialog with web interface URL - - Auto-opens web interface in browser - - No QR code scanning needed! - -### User Experience - -The app provides a **100% GUI experience** with NO Terminal window: - -- **All inputs** collected via AppleScript dialogs -- **Installation runs in background** - user never sees command line -- **Progress feedback** via non-blocking GUI dialogs -- **Error handling** with clear, actionable error messages in dialogs -- **Success confirmation** with direct link to web interface -- **Auto-launch** of web interface when installation completes - -This approach provides maximum ease of use for non-technical users while maintaining security and proper error handling. - -## Files - -- `install-wrapper.sh` - Main wrapper script with fully GUI installation flow -- `build-macos-app.sh` - Build script that creates the .app bundle -- `README.md` - This file - -## Customization - -### Changing the App Name - -Edit `build-macos-app.sh`: -```bash -APP_NAME="Your App Name" -``` - -### Changing the Bundle Identifier - -Edit `build-macos-app.sh`: -```bash -BUNDLE_ID="com.yourcompany.yourapp" -``` - -### Changing the Icon - -Replace or update the icon path in `build-macos-app.sh`: -```bash -ICON_FILE="$REPO_ROOT/resources/YourIcon.icns" -``` - -To create a new icon from a PNG: -```bash -# Create iconset directory -mkdir MyIcon.iconset - -# Create multiple sizes (512x512 PNG as source) -sips -z 16 16 icon.png --out MyIcon.iconset/icon_16x16.png -sips -z 32 32 icon.png --out MyIcon.iconset/icon_16x16@2x.png -sips -z 32 32 icon.png --out MyIcon.iconset/icon_32x32.png -sips -z 64 64 icon.png --out MyIcon.iconset/icon_32x32@2x.png -sips -z 128 128 icon.png --out MyIcon.iconset/icon_128x128.png -sips -z 256 256 icon.png --out MyIcon.iconset/icon_128x128@2x.png -sips -z 256 256 icon.png --out MyIcon.iconset/icon_256x256.png -sips -z 512 512 icon.png --out MyIcon.iconset/icon_256x256@2x.png -sips -z 512 512 icon.png --out MyIcon.iconset/icon_512x512.png -sips -z 1024 1024 icon.png --out MyIcon.iconset/icon_512x512@2x.png - -# Convert to icns -iconutil -c icns MyIcon.iconset -``` - -## Troubleshooting - -### App won't open: "App is damaged" - -This happens because the app isn't code-signed. Users need to: -1. Right-click the app -2. Select "Open" -3. Click "Open" in the security dialog - -Or remove quarantine attribute: -```bash -xattr -cr "/Applications/MeticAI Installer.app" -``` - -### Platypus not found - -Install Platypus: -```bash -brew install platypus -``` - -Or let the build script create the app manually (no Platypus required). - -### Build fails - -Check that: -- You're in the `macos-installer` directory -- `install-wrapper.sh` exists and is readable -- The icon file exists at `../resources/MeticAI.icns` - -### Installer doesn't work - -The app requires: -- Git installed (`xcode-select --install`) -- Docker Desktop installed and running -- Internet connection to download the installer script - -## Code Signing and Notarization (Optional) - -Code signing requires a **paid Apple Developer Program membership ($99/year)**. Without signing, the apps work perfectly - users just need to right-click → Open once to bypass Gatekeeper. - -| Feature | Without Signing | With Signing ($99/year) | -|---------|-----------------|-------------------------| -| App works | ✅ | ✅ | -| Double-click to open | ❌ (right-click → Open) | ✅ | -| No security warnings | ❌ | ✅ | -| Suitable for | Personal use, small audience | Public distribution | - -### For Unsigned Apps (Default) - -Users can open unsigned apps by: -1. Right-click the app → Select "Open" → Click "Open" in the dialog -2. Or run: `xattr -cr "/Applications/MeticAI Installer.app"` - -### Local Code Signing (If you have a Developer account) - -```bash -# Sign the apps -codesign --deep --force --verify --verbose \ - --sign "Developer ID Application: Your Name (TEAM_ID)" \ - "build/MeticAI Installer.app" - -codesign --deep --force --verify --verbose \ - --sign "Developer ID Application: Your Name (TEAM_ID)" \ - "build/MeticAI Uninstaller.app" -``` - -### Notarization - -```bash -# Create the DMG first -hdiutil create -volname "MeticAI" -srcfolder build -ov -format UDZO "build/MeticAI.dmg" - -# Submit for notarization -xcrun notarytool submit "build/MeticAI.dmg" \ - --apple-id "your@email.com" \ - --team-id "TEAM_ID" \ - --password "app-specific-password" \ - --wait - -# Staple the notarization ticket -xcrun stapler staple "build/MeticAI.dmg" -``` - -### GitHub Actions (Automated) - -The repository includes a GitHub Action that automatically builds and releases the DMG on every push to `main` that modifies the installer scripts. **No configuration needed for unsigned builds.** - -#### Optional: Enable Code Signing - -To enable code signing in GitHub Actions (requires $99/year Apple Developer account), add these secrets: - -| Secret | Description | -|--------|-------------| -| `MACOS_CERTIFICATE` | Base64-encoded .p12 certificate file | -| `MACOS_CERTIFICATE_PWD` | Password for the .p12 certificate | -| `KEYCHAIN_PWD` | A random password for the temporary keychain | -| `SIGNING_IDENTITY` | The certificate name, e.g., "Developer ID Application: Your Name (TEAM_ID)" | -| `APPLE_ID` | Your Apple ID email (for notarization) | -| `APPLE_ID_PASSWORD` | App-specific password (generate at appleid.apple.com) | -| `APPLE_TEAM_ID` | Your Apple Developer Team ID | - -#### Creating the Certificate Secret - -1. Export your "Developer ID Application" certificate from Keychain Access as a .p12 file -2. Base64 encode it: - ```bash - base64 -i certificate.p12 | pbcopy - ``` -3. Paste the result as the `MACOS_CERTIFICATE` secret - -#### Without Signing (Default) - -The GitHub Action will still build the DMG - it just won't be signed. Users will need to right-click and select "Open" to bypass Gatekeeper, which is fine for most use cases. - -## Automated Releases - -The GitHub Action (`.github/workflows/build-macos-installer.yml`) automatically: - -1. **Triggers on changes** to `macos-installer/`, `local-install.sh`, or `uninstall.sh` -2. **Builds both apps** using Platypus on macOS -3. **Signs the apps** (if certificates are configured) -4. **Creates the DMG** -5. **Notarizes** (if Apple credentials are configured) -6. **Creates a GitHub Release** with the DMG attached - -## Future Enhancements - -Potential improvements for the macOS installer: - -- [ ] Collect API key and IPs in the GUI (avoid Terminal entirely) -- [ ] Show installation progress in a native progress bar -- [ ] Auto-detect and install prerequisites silently -- [ ] Include offline installer option (bundle all dependencies) -- [ ] Add uninstaller app -- [ ] Create update checker app -- [ ] Support multiple languages - -## License - -This installer is part of MeticAI and follows the same license as the main project. - -## Credits - -Built with: -- **Platypus** - Create Mac applications from scripts (optional) -- **AppleScript** - Native macOS dialogs and user interaction -- **Bash** - Installation logic and orchestration diff --git a/macos-installer/build-macos-app.sh b/macos-installer/build-macos-app.sh deleted file mode 100755 index e9b02c2..0000000 --- a/macos-installer/build-macos-app.sh +++ /dev/null @@ -1,194 +0,0 @@ -#!/bin/bash - -################################################################################ -# MeticAI - macOS App Builder Script -################################################################################ -# -# This script builds a standalone macOS .app bundle for MeticAI installer -# using Platypus (or manually if Platypus is not available). -# -# USAGE: -# ./build-macos-app.sh -# -# PREREQUISITES: -# - Platypus installed (recommended): brew install platypus -# - OR: Manual .app bundle creation (automatic fallback) -# -################################################################################ - -set -e - -# Colors -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -APP_NAME="MeticAI Installer" -BUNDLE_ID="com.meticai.installer" -VERSION="1.0.0" -OUTPUT_DIR="$SCRIPT_DIR/build" -APP_PATH="$OUTPUT_DIR/${APP_NAME}.app" -# Executable name without spaces for compatibility -EXEC_NAME="MeticAI-Installer" - -# Script and resources -WRAPPER_SCRIPT="$SCRIPT_DIR/install-wrapper.sh" -ICON_FILE="$REPO_ROOT/resources/MeticAI.icns" -LOCAL_INSTALL_SCRIPT="$REPO_ROOT/local-install.sh" - -echo -e "${BLUE}=========================================${NC}" -echo -e "${BLUE} MeticAI macOS App Builder${NC}" -echo -e "${BLUE}=========================================${NC}" -echo "" - -# Check if wrapper script exists -if [ ! -f "$WRAPPER_SCRIPT" ]; then - echo -e "${RED}Error: Wrapper script not found at $WRAPPER_SCRIPT${NC}" - exit 1 -fi - -# Check if icon exists -if [ ! -f "$ICON_FILE" ]; then - echo -e "${YELLOW}Warning: Icon file not found at $ICON_FILE${NC}" - echo -e "${YELLOW}App will be created without custom icon${NC}" - ICON_FILE="" -fi - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Check for Platypus -if command -v platypus &> /dev/null; then - echo -e "${GREEN}✓ Platypus found${NC}" - echo -e "${YELLOW}Building app with Platypus...${NC}" - - # Build with Platypus - platypus_args=( - --name "$APP_NAME" - --bundle-identifier "$BUNDLE_ID" - --app-version "$VERSION" - --author "MeticAI" - --interface-type "Progress Bar" - --interpreter "/bin/bash" - --quit-after-execution - --overwrite - ) - - # Add icon if available - if [ -n "$ICON_FILE" ]; then - platypus_args+=(--app-icon "$ICON_FILE") - fi - - # Bundle local-install.sh as a resource - if [ -f "$LOCAL_INSTALL_SCRIPT" ]; then - platypus_args+=(--bundled-file "$LOCAL_INSTALL_SCRIPT") - fi - - # Add script - platypus_args+=("$WRAPPER_SCRIPT" "$APP_PATH") - - # Execute platypus - if platypus "${platypus_args[@]}"; then - echo -e "${GREEN}✓ App built successfully with Platypus${NC}" - else - echo -e "${RED}Error: Platypus build failed${NC}" - exit 1 - fi - -else - echo -e "${YELLOW}Platypus not found. Building app manually...${NC}" - echo -e "${BLUE}Tip: Install Platypus for better app creation: brew install platypus${NC}" - echo "" - - # Manual .app bundle creation - echo "Creating app bundle structure..." - - # Remove old app if exists - rm -rf "$APP_PATH" - - # Create bundle structure - mkdir -p "$APP_PATH/Contents/MacOS" - mkdir -p "$APP_PATH/Contents/Resources" - - # Copy wrapper script as executable - cp "$WRAPPER_SCRIPT" "$APP_PATH/Contents/MacOS/${EXEC_NAME}" - chmod +x "$APP_PATH/Contents/MacOS/${EXEC_NAME}" - - # Copy icon if available - if [ -n "$ICON_FILE" ]; then - cp "$ICON_FILE" "$APP_PATH/Contents/Resources/AppIcon.icns" - ICON_KEY="CFBundleIconFile - AppIcon" - else - ICON_KEY="" - fi - - # Create Info.plist - cat > "$APP_PATH/Contents/Info.plist" < - - - - CFBundleExecutable - ${EXEC_NAME} - CFBundleIdentifier - ${BUNDLE_ID} - CFBundleName - ${APP_NAME} - CFBundleDisplayName - ${APP_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - ${VERSION} - CFBundleVersion - ${VERSION} - ${ICON_KEY} - LSMinimumSystemVersion - 10.15 - NSHighResolutionCapable - - LSApplicationCategoryType - public.app-category.utilities - LSUIElement - - - -EOF - - echo -e "${GREEN}✓ App bundle created manually${NC}" -fi - -# Verify the app was created -if [ ! -d "$APP_PATH" ]; then - echo -e "${RED}Error: App bundle was not created${NC}" - exit 1 -fi - -# Make the app executable (verify file exists first for better error handling) -if [ -f "$APP_PATH/Contents/MacOS/${EXEC_NAME}" ]; then - chmod +x "$APP_PATH/Contents/MacOS/${EXEC_NAME}" -else - echo -e "${YELLOW}Warning: Executable file not found at expected location${NC}" -fi - -echo "" -echo -e "${GREEN}=========================================${NC}" -echo -e "${GREEN} ✓ Build Complete!${NC}" -echo -e "${GREEN}=========================================${NC}" -echo "" -echo -e "App location: ${BLUE}$APP_PATH${NC}" -echo "" -echo -e "${YELLOW}Next steps:${NC}" -echo "1. Test the app: open \"$APP_PATH\"" -echo "2. Move to Applications: mv \"$APP_PATH\" /Applications/" -echo "3. Distribute: Create a DMG or ZIP file" -echo "" -echo -e "${YELLOW}To create a distributable DMG:${NC}" -echo " hdiutil create -volname \"MeticAI Installer\" -srcfolder \"$APP_PATH\" -ov -format UDZO \"$OUTPUT_DIR/MeticAI-Installer.dmg\"" -echo "" diff --git a/macos-installer/build-uninstaller-app.sh b/macos-installer/build-uninstaller-app.sh deleted file mode 100755 index a4cc6d4..0000000 --- a/macos-installer/build-uninstaller-app.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash - -################################################################################ -# MeticAI - macOS Uninstaller App Builder Script -################################################################################ -# -# This script builds a standalone macOS .app bundle for MeticAI uninstaller -# using Platypus (or manually if Platypus is not available). -# -# USAGE: -# ./build-uninstaller-app.sh -# -# PREREQUISITES: -# - Platypus installed (recommended): brew install platypus -# - OR: Manual .app bundle creation (automatic fallback) -# -################################################################################ - -set -e - -# Colors -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -APP_NAME="MeticAI Uninstaller" -BUNDLE_ID="com.meticai.uninstaller" -VERSION="1.0.0" -OUTPUT_DIR="$SCRIPT_DIR/build" -APP_PATH="$OUTPUT_DIR/${APP_NAME}.app" -# Executable name without spaces for compatibility -EXEC_NAME="MeticAI-Uninstaller" - -# Script and resources -WRAPPER_SCRIPT="$SCRIPT_DIR/uninstall-wrapper.sh" -ICON_FILE="$REPO_ROOT/resources/MeticAI.icns" - -echo -e "${BLUE}=========================================${NC}" -echo -e "${BLUE} MeticAI Uninstaller App Builder${NC}" -echo -e "${BLUE}=========================================${NC}" -echo "" - -# Check if wrapper script exists -if [ ! -f "$WRAPPER_SCRIPT" ]; then - echo -e "${RED}Error: Wrapper script not found at $WRAPPER_SCRIPT${NC}" - exit 1 -fi - -# Check if icon exists -if [ ! -f "$ICON_FILE" ]; then - echo -e "${YELLOW}Warning: Icon file not found at $ICON_FILE${NC}" - echo -e "${YELLOW}App will be created without custom icon${NC}" - ICON_FILE="" -fi - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Check for Platypus -if command -v platypus &> /dev/null; then - echo -e "${GREEN}✓ Platypus found${NC}" - echo -e "${YELLOW}Building app with Platypus...${NC}" - - # Build with Platypus - platypus_args=( - --name "$APP_NAME" - --bundle-identifier "$BUNDLE_ID" - --app-version "$VERSION" - --author "MeticAI" - --interface-type "Progress Bar" - --interpreter "/bin/bash" - --quit-after-execution - --overwrite - ) - - # Add icon if available - if [ -n "$ICON_FILE" ]; then - platypus_args+=(--app-icon "$ICON_FILE") - fi - - # Add script - platypus_args+=("$WRAPPER_SCRIPT" "$APP_PATH") - - # Execute platypus - if platypus "${platypus_args[@]}"; then - echo -e "${GREEN}✓ App built successfully with Platypus${NC}" - else - echo -e "${RED}Error: Platypus build failed${NC}" - exit 1 - fi - -else - echo -e "${YELLOW}Platypus not found. Building app manually...${NC}" - echo -e "${BLUE}Tip: Install Platypus for better app creation: brew install platypus${NC}" - echo "" - - # Manual .app bundle creation - echo "Creating app bundle structure..." - - # Remove old app if exists - rm -rf "$APP_PATH" - - # Create bundle structure - mkdir -p "$APP_PATH/Contents/MacOS" - mkdir -p "$APP_PATH/Contents/Resources" - - # Copy wrapper script as executable - cp "$WRAPPER_SCRIPT" "$APP_PATH/Contents/MacOS/${EXEC_NAME}" - chmod +x "$APP_PATH/Contents/MacOS/${EXEC_NAME}" - - # Copy icon if available - if [ -n "$ICON_FILE" ]; then - cp "$ICON_FILE" "$APP_PATH/Contents/Resources/AppIcon.icns" - ICON_KEY="CFBundleIconFile - AppIcon" - else - ICON_KEY="" - fi - - # Create Info.plist - cat > "$APP_PATH/Contents/Info.plist" < - - - - CFBundleExecutable - ${EXEC_NAME} - CFBundleIdentifier - ${BUNDLE_ID} - CFBundleName - ${APP_NAME} - CFBundleDisplayName - ${APP_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - ${VERSION} - CFBundleVersion - ${VERSION} - ${ICON_KEY} - LSMinimumSystemVersion - 10.15 - NSHighResolutionCapable - - LSApplicationCategoryType - public.app-category.utilities - LSUIElement - - - -EOF - - echo -e "${GREEN}✓ App bundle created manually${NC}" -fi - -# Verify the app was created -if [ ! -d "$APP_PATH" ]; then - echo -e "${RED}Error: App bundle was not created${NC}" - exit 1 -fi - -# Make the app executable (verify file exists first for better error handling) -if [ -f "$APP_PATH/Contents/MacOS/${EXEC_NAME}" ]; then - chmod +x "$APP_PATH/Contents/MacOS/${EXEC_NAME}" -else - echo -e "${YELLOW}Warning: Executable file not found at expected location${NC}" -fi - -echo "" -echo -e "${GREEN}=========================================${NC}" -echo -e "${GREEN} ✓ Build Complete!${NC}" -echo -e "${GREEN}=========================================${NC}" -echo "" -echo -e "App location: ${BLUE}$APP_PATH${NC}" -echo "" -echo -e "${YELLOW}Next steps:${NC}" -echo "1. Test the app: open \"$APP_PATH\"" -echo "2. Move to Applications: mv \"$APP_PATH\" /Applications/" -echo "3. Distribute: Create a DMG or ZIP file" -echo "" -echo -e "${YELLOW}To create a distributable DMG:${NC}" -echo " hdiutil create -volname \"MeticAI Uninstaller\" -srcfolder \"$APP_PATH\" -ov -format UDZO \"$OUTPUT_DIR/MeticAI-Uninstaller.dmg\"" -echo "" diff --git a/macos-installer/install-wrapper.sh b/macos-installer/install-wrapper.sh deleted file mode 100755 index b2f98a8..0000000 --- a/macos-installer/install-wrapper.sh +++ /dev/null @@ -1,749 +0,0 @@ -#!/bin/bash - -################################################################################ -# MeticAI - macOS Installer Wrapper Script (Fully GUI) -################################################################################ -# -# This script provides a completely GUI-based installation experience for macOS -# users with NO Terminal window. All inputs are collected via AppleScript dialogs, -# and installation runs in the background with progress feedback via GUI. -# -# This script is designed to be packaged with Platypus to create a standalone -# macOS .app bundle. -# -################################################################################ - -# Exit on error -set -e - -# Set PATH to ensure we can find Docker and other tools -# Docker Desktop installs to /Applications/Docker.app -export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Docker.app/Contents/Resources/bin:$PATH" - -# Get icon path for dialogs - set globally at startup -get_icon_path() { - # Try to find the app bundle we're running from - # Platypus bundles script in Contents/Resources/script - # Manual bundles put it in Contents/MacOS/ - if [[ "$0" == *"/Contents/Resources/"* ]]; then - # Platypus bundle - script is in Contents/Resources/script - local bundle_path=$(echo "$0" | sed 's|/Contents/Resources/.*||') - local icon_path="$bundle_path/Contents/Resources/AppIcon.icns" - - if [ -f "$icon_path" ]; then - echo "$icon_path" - return - fi - elif [[ "$0" == *"/Contents/MacOS/"* ]]; then - # Manual bundle - script is in Contents/MacOS/ - local bundle_path=$(echo "$0" | sed 's|/Contents/MacOS/.*||') - local icon_path="$bundle_path/Contents/Resources/AppIcon.icns" - - if [ -f "$icon_path" ]; then - echo "$icon_path" - return - fi - fi - - # Fallback - empty means use system icon - echo "" -} - -# Set global icon path at startup -ICON_PATH=$(get_icon_path) - -# Get the Resources directory for bundled files (Platypus bundles files there) -get_resource_dir() { - if [[ "$0" == *"/Contents/Resources/"* ]]; then - # Platypus bundle - bundled files are in Contents/Resources/ - echo "$0" | sed 's|/Contents/Resources/.*|/Contents/Resources|' - elif [[ "$0" == *"/Contents/MacOS/"* ]]; then - # Manual bundle - bundled files would be in Contents/Resources/ - echo "$0" | sed 's|/Contents/MacOS/.*|/Contents/Resources|' - else - echo "" - fi -} - -# Set global resource directory at startup -SCRIPT_RESOURCE_DIR=$(get_resource_dir) - -# Logging functions - use >&2 to avoid capturing in command substitution -log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >&2 -} - -log_error() { - echo "ERROR: $1" >&2 -} - -# Show progress dialog (non-blocking) -show_progress() { - local message="$1" - echo "PROGRESS: $message" -} - -# Display welcome dialog -show_welcome() { - if [ -n "$ICON_PATH" ]; then - osascript < /dev/null; then - log_message "Git not found" - missing_tools+=("Git") - else - log_message "Git found at: $(command -v git)" - fi - - # Check for docker - both installed AND running - if ! command -v docker &> /dev/null; then - log_message "Docker command not found in PATH" - missing_tools+=("Docker Desktop") - else - local docker_path=$(command -v docker) - log_message "Docker found at: $docker_path" - - # Docker command exists, but check if daemon is running - if ! docker info &> /dev/null 2>&1; then - log_message "Docker daemon not responding to 'docker info'" - docker_not_running=true - else - log_message "Docker daemon is running" - fi - fi - - log_message "Prerequisite check complete. Missing tools: ${missing_tools[*]:-none}, Docker not running: $docker_not_running" - if [ ${#missing_tools[@]} -gt 0 ] || [ "$docker_not_running" = true ]; then - local missing_list=$(IFS=", "; echo "${missing_tools[*]}") - - if [ "$docker_not_running" = true ] && [ ${#missing_tools[@]} -eq 0 ]; then - # Docker is installed but not running - osascript <<'EOF' -tell application "System Events" - activate - display dialog "Docker Desktop Not Running - -Docker Desktop is installed but not running. - -Please start Docker Desktop before continuing with the installation. - -You can find Docker Desktop in your Applications folder." buttons {"Cancel", "OK"} default button "OK" with icon caution with title "MeticAI Installer" -end tell -EOF - exit 1 - else - # Some tools are missing - local dialog_result=$(osascript <