Как стать автором
Поиск
Написать публикацию
Обновить

Githab CLI и Github Actions на страже вайб кодинга с мобильными агентами Cursor

Уровень сложностиСредний
Время на прочтение22 мин
Количество просмотров1.7K

Меня зовут Авенир Воронов и я отвечаю за Лабораторию инноваций в компании "ДАР". В этой статье я расскажу про опыт работы с агентами курсора и великолепный Github Cli, который сэкономил кучу времени.

Вступление

Лето. Время отпусков. Делать нечего.

Можно залипать в рилсах или сериальчиках на пляже, но зачем, если можно создать что-то прикольное с мобильного через агентов. Начнем с самого простого (стоит 20 USD в месяц или 3 тыс руб в год, если немного поискать).

Первый промпт
Первый промпт

Итак создаем репозиторий (в моем случае такой) или берем готовый. Даем задачу агенту создать игру.

Выбор моделей
Выбор моделей

Пока агент делает игру немного поговорим про UI. Агент изначально работает на Claude Sonnet. Когда токены заканчиваются, предлагает перейти на Auto режим.

Агенты могут работать параллельно. Я рекомендую вести древовидные записи задач для агентов, чтобы самому не потеряться. Иначе уже при запуске 3-4 агентов можно забыть с кем и о чем договаривался. Агенты любят:

  1. Неправильно выбирать решение

  2. Не доделывать работу до конца

  3. Не запускать код у себя перед тем, как передать в тестирование

  4. Запускать код у себя, но не передавать в тестирование

  5. Проявлять излишнюю инициативу

  6. Не проявлять инициативу и просить у вас подтверждение на каждое действиею

Поэтому надо четко отслеживать статус ваших взаимоотношений с каждым агентом. Или хотя бы в Obsidian записывать.

Чат
Чат

Для общения с агентами есть две вкладки. Первое — чат, в котором существует возможность в ходе работы спрашивать «Получается ли?»

Вторая вкладка, Diff, позволяет видеть результаты действий агента.

В итоге можно сделать Pull Request и Merge прямо в агенте.

Также стоит учитывать следующие вещи:

1. Агенты порождают ветки независимо от первоначальных настроек. Если за этим не следить, можно остаться с большим количеством веток с абсолютно ничего не говорящими названиями.

2. Агенты могут терять связь с Git, но способны работать с ним через стандартные консольные инструменты. Иногда это превращается в необходимость. Например, кнопка создания Pull Request может не работать без объяснения причины. Тогда следует подсказать агенту, где почитать инструкции, дать нужные адреса, токены. Это может привести к небольшой анархии в репозиториях, поэтому следите за правами, которые вы даете агентам.

В опциях к агентам есть экспериментальная функция BugBot, которая находит в вашем коде баги. Функция хорошая, так как проверяет в том числе безопасность вашего кода, а не только отвалившиеся кнопки на главном экране. Включается она на вкладе Dashboard.

Пока мы изучали агента, получилось некоторое подобие игры, в которую даже любопытно играть самому.

Можно показать знакомым, собрать обратную связь и добавить функции.

Переходим к дизайну и просим агентов добавить пустые картинки в проект и написать промпты для Midjourney для каждой картинки, с указанием размеров. Готовые промпты и адреса картинок сгружаем в md файл.

Главное меню похорошело, как Москва
Главное меню похорошело, как Москва

Пересылаем промпты в свой генератор и сохраняем в игру.

Добавился кастом
Добавился кастом

Агент довольно хорошо «обошелся» со скроллами. Вертикальные прокрутки поставил на все меню, а горизонтальные на подменю (выбор профессии, мечты, навыков и т. д.)

Выяснили стоимость хард и софт скиллов
Выяснили стоимость хард и софт скиллов

Практически все идеи по кастомизации исходили от агента.

В частности, оценка навыков в рублях и статистика продолжительности жизни были сгенерированы на основе поиска статистики в сети.

Посмеялись над ценами
Посмеялись над ценами

Текст на плашках изначально выходил за края. Но достаточно было указать на этот факт, чтобы агент всё исправил.

Получили игровой экран
Получили игровой экран

Интерфейс основного экрана получился практически с первого раза. Все доработки совершал сам агент без необходимости открывать верстку.

Так видит кубик Midjourney
Так видит кубик Midjourney

На этом этапе самым сложным было объяснить агентам, что такое игровой кубик. Нейросети не понимали принципа расположения точек.

Вариант с генерацией трехмерных кубиков через mesh.ai тоже не увенчался успехом. Кубики выходили странными, точки плыли по граням.

Так видит Meshy.ai
Так видит Meshy.ai

Мы с агентом попробовали написать код для трехмерного куба и натянуть текстуру через питон, но в итоге «сошлись» на простом скрипте для блендера. Cursor сгенерировал скрипт сразу хорошо.

Скрипт агента отрабатывает в блендере с первого раза. Точки сделаны шариками. Браво, агент!
Скрипт агента отрабатывает в блендере с первого раза. Точки сделаны шариками. Браво, агент!

К этому моменту проект уже разросся, и я попросил описать архитектуру, чтобы понимать, что вообще происходит. Расписать попросил по C4. И вот что получилось:

Первый слой из одного квадрата.

Уровень контекста
Уровень контекста

Пользователь взаимодействует с приложением через графический интерфейс.

Второй слой агент описал одним контейнером с 4 слоями. Вполне логично для такого простого приложения.

Уровень контейнеров
Уровень контейнеров

UI слой: Активности (MainActivity, GameActivity, CharacterCreationActivity, ProfessionSelectionActivity, RulesActivity) и layout-файлы.

Логика игры: GameManager, модели (Player, GameState, Card, Asset и др.), обработка событий, расчёты.

Данные: GameDataManager (предоставляет профессии, мечты и др.), хранение состояния в памяти (Parcelable)

Ресурсы: Изображения, иконки, стили, строки, layout-описания

На третьем слое агент нарисовал 4 компонента.

1. UI-компоненты (экраны, интерфейс)

UI-компоненты
UI-компоненты
  • MainActivity — главный экран

  • GameActivity — основной игровой процесс

  • CharacterCreationActivity — расширенное создание персонажа

  • ProfessionSelectionActivity — выбор профессии и мечты

  • RulesActivity — экран с правилами

  • Adapters (отображение списков):

    • adapters/AssetAdapter.kt

    • adapters/ProfessionAdapter.kt

    • adapters/DreamAdapter.kt

    • (и другие в adapters/)

2. Компонент бизнес-логики

GameManager — управление состоянием игры

3. Компонент моделей (структура данных)

  • models/Player.kt — игрок

  • models/GameState.kt — состояние игры

  • models/Card.kt — карточки, профессии, мечты, активы, пассивы, инвестиции

  • models/Asset.kt, models/Liability.kt, models/Investment.kt — отдельные сущности

  • models/FinancialEntry.kt — финансовый журнал

  • models/ProfessionalRisk.kt — профессиональные риски

  • (и другие в models/)

4. Компонент данных

  • data/GameDataManager.kt — статические игровые данные

  • data/FinancialTemplates.kt — шаблоны финансовых профилей

Дальше можно продолжать и без агента. Но захотелось ускорить процесс работы.

Про CICD и Github

Первой мыслью было - написать пайпы CICD, которые позволили бы тестировать apk автоматически. Чтобы сделать CICD в GitHub используются Github Actions.

Это Yaml, который лежит в .github/workflows

  • Workflow (рабочий процесс) — файл YAML.

  • Event (событие) — триггер, по которому стартует workflow (например, push, pull_request, issue). Задается в том же yaml по слову on:

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:
  • Job (задача) — логически обособленный набор шагов (steps), выполняющихся параллельно или последовательно.

jobs:
  # Основная сборка и тестирование
  build-and-test:
...
  # Параллельная генерация скриншотов для телефона
  screenshots-phone:
...
  • Step (шаг) — отдельная инструкция: скрипт shell, исполнение actions, использование стороннего действия.

    - name: 🔧 Grant execute permission for gradlew
      run: chmod +x gradlew

Пример Workflow:

name: 🚀 Stable Build & Release

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

env:
  GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true
  MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED: "true"

jobs:
  # Основная сборка и тестирование
  build-and-test:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    outputs:
      version: ${{ steps.version.outputs.version }}
      version_code: ${{ steps.version.outputs.version_code }}
      date: ${{ steps.date.outputs.date }}
      apk_path: ${{ steps.apk_path.outputs.path }}
    # should_release больше не нужен

    steps:
    - name: 📥 Checkout code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0  # Получаем полную историю для changelog

    - name: 🔍 Determine if this is a release build
      id: should_release
      run: |
        if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "value=true" >> $GITHUB_OUTPUT
        else
          echo "value=false" >> $GITHUB_OUTPUT
        fi

    - name: 🔖 Bump version (only for releases)
      id: bump_version
      if: steps.should_release.outputs.value == 'true'
      run: |
        # Получаем текущую версию
        CURR_VERSION=$(grep -oP 'versionName\s+"\K[^"]+' app/build.gradle)
        CURR_CODE=$(grep -oP 'versionCode\s+\K[0-9]+' app/build.gradle)
        
        # Инкрементируем minor версию (x.y -> x.(y+1))
        MAJOR=$(echo $CURR_VERSION | cut -d. -f1)
        MINOR=$(echo $CURR_VERSION | cut -d. -f2)
        if [ -z "$MINOR" ]; then MINOR=0; fi
        NEW_MINOR=$((MINOR+1))
        NEW_VERSION="$MAJOR.$NEW_MINOR"
        NEW_CODE=$((CURR_CODE+1))
        
        # Обновляем build.gradle
        sed -i "s/versionName \"$CURR_VERSION\"/versionName \"$NEW_VERSION\"/" app/build.gradle
        sed -i "s/versionCode $CURR_CODE/versionCode $NEW_CODE/" app/build.gradle
        
        echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
        echo "new_code=$NEW_CODE" >> $GITHUB_OUTPUT
        echo "Version bumped: $CURR_VERSION → $NEW_VERSION ($CURR_CODE → $NEW_CODE)"
        
        # Коммитим изменения
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add app/build.gradle
        git commit -m "🔖 Bump version to $NEW_VERSION ($NEW_CODE) [auto]" || echo "No version change to commit"
        git push || echo "No changes to push"

    - name: 📝 Generate release notes (only for releases)
      id: release_notes
      if: steps.should_release.outputs.value == 'true'
      run: |
        echo "## 🎮 Financial Success v${{ steps.bump_version.outputs.new_version }}" > RELEASE_NOTES.md
        echo "" >> RELEASE_NOTES.md
        echo "### 📅 Дата релиза: $(date '+%Y-%m-%d %H:%M UTC')" >> RELEASE_NOTES.md
        echo "" >> RELEASE_NOTES.md
        echo "### 🔄 Последние изменения:" >> RELEASE_NOTES.md
        git log --oneline -10 >> RELEASE_NOTES.md
        echo "" >> RELEASE_NOTES.md
        echo "### 📋 Изменения из CHANGELOG.md:" >> RELEASE_NOTES.md
        if [ -f "docs/CHANGELOG.md" ]; then
          tail -n 20 docs/CHANGELOG.md >> RELEASE_NOTES.md
        fi
        cat RELEASE_NOTES.md

    - name: 📝 Commit release notes (only for releases)
      if: steps.should_release.outputs.value == 'true'
      run: |
        git add RELEASE_NOTES.md
        git commit -m "📝 Update release notes [auto]" || echo "No release notes change to commit"
        git push || echo "No changes to push"

    - name: ☕ Setup JDK 21
      uses: actions/setup-java@v4
      with:
        java-version: '21'
        distribution: 'temurin'

    - name: 🔍 Check dependencies
      run: |
        echo "🔍 Checking dependencies..."
        
        # Проверка Java
        java_version=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1)
        echo "☕ Java version: $java_version"
        if [ "$java_version" != "21" ]; then
          echo "⚠️ Warning: Recommended Java 21, found: $java_version"
        fi
        
        # Проверка gradlew
        if [ ! -f "gradlew" ]; then
          echo "❌ Error: gradlew not found"
          exit 1
        fi
        
        echo "✅ Dependencies checked"

    - name: 🤖 Setup Android SDK
      uses: android-actions/setup-android@v3

    - name: 📦 Cache Gradle packages
      uses: actions/cache@v3
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
          ~/.android/build-cache
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-

    - name: 🔧 Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: � Clean project
      run: |
        echo "🧹 Cleaning project..."
        ./gradlew clean --no-daemon
        if [ $? -eq 0 ]; then
          echo "✅ Project cleaned successfully"
        else
          echo "❌ Project clean failed"
          exit 1
        fi

    - name: �� Run Unit Tests
      run: |
        echo "🧪 Running unit tests..."
        ./gradlew test --no-daemon
        if [ $? -eq 0 ]; then
          echo "✅ Unit tests passed"
        else
          echo "❌ Unit tests failed"
          exit 1
        fi

    - name: 🔨 Build Debug APK
      run: |
        echo "🔨 Building debug APK..."
        ./gradlew assembleDebug --no-daemon
        if [ $? -eq 0 ]; then
          echo "✅ Debug APK built successfully"
        else
          echo "❌ Debug APK build failed"
          exit 1
        fi

    - name: 📊 Get version info
      id: version
      run: |
        VERSION=$(grep -oP 'versionName\s+"\K[^"]+' app/build.gradle)
        VERSION_CODE=$(grep -oP 'versionCode\s+\K[0-9]+' app/build.gradle)
        echo "version=$VERSION" >> $GITHUB_OUTPUT
        echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT
        echo "Version: $VERSION ($VERSION_CODE)"

    - name: 📅 Get current date
      id: date
      run: echo "date=$(date +'%Y-%m-%d_%H-%M')" >> $GITHUB_OUTPUT

    - name: 📱 Prepare APK for distribution
      id: apk_path
      run: |
        echo "📱 Preparing APK for distribution..."
        
        # Проверяем, что APK существует
        if [ ! -f "app/build/outputs/apk/debug/app-debug.apk" ]; then
          echo "❌ Error: Debug APK not found"
          ls -la app/build/outputs/apk/debug/ || echo "Debug directory not found"
          exit 1
        fi
        
        mkdir -p releases/debug
        cp app/build/outputs/apk/debug/app-debug.apk releases/debug/
        cd releases/debug
        
        # Переименовываем с версией и датой
        newname="FinancialSuccess-v${{ steps.version.outputs.version }}-${{ steps.date.outputs.date }}-debug.apk"
        mv -f app-debug.apk "$newname"
        echo "path=releases/debug/$newname" >> $GITHUB_OUTPUT
        
        # Создаем символическую ссылку на latest
        ln -sf "$newname" "latest-debug.apk"
        
        # Очищаем старые APK (оставляем только 5 последних)
        ls -tp FinancialSuccess-v*-debug.apk | grep -v '/$' | tail -n +6 | xargs -r rm -- 2>/dev/null || true
        
        echo "✅ APK prepared: $newname"
        echo "📊 APK files:"
        ls -la

    - name: 📤 Upload APK Artifact
      uses: actions/upload-artifact@v4
      with:
        name: FinancialSuccess-v${{ steps.version.outputs.version }}-${{ steps.date.outputs.date }}
        path: releases/debug/*.apk
        retention-days: 30

    - name: 💾 Commit APK files (only for releases)
      if: steps.should_release.outputs.value == 'true'
      run: |
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add releases/
        if git diff --staged --quiet; then
          echo "No changes to commit"
        else
          git commit -m "📱 Auto-commit APK v${{ steps.version.outputs.version }} - ${{ steps.date.outputs.date }}
          
          🔧 Build info:
          - Version: ${{ steps.version.outputs.version }}
          - Version Code: ${{ steps.version.outputs.version_code }}
          - Date: ${{ steps.date.outputs.date }}
          - Branch: ${{ github.ref_name }}
          - Commit: ${{ github.sha }}"
          git push
        fi

  # Параллельная генерация скриншотов для телефона
  screenshots-phone:
    runs-on: ubuntu-latest
    needs: build-and-test
    # Убрано условие should_release
    
    steps:
    - name: 📥 Checkout code
      uses: actions/checkout@v4

    - name: 📥 Download APK
      uses: actions/download-artifact@v4
      with:
        name: FinancialSuccess-v${{ needs.build-and-test.outputs.version }}-${{ needs.build-and-test.outputs.date }}

    - name: 🔧 Install Android SDK and platform-tools
      env:
        ANDROID_SDK_ROOT: ${{ runner.home }}/android-sdk
        ANDROID_HOME: ${{ runner.home }}/android-sdk
        PATH: ${{ runner.home }}/android-sdk/emulator:${{ runner.home }}/android-sdk/platform-tools:${{ runner.home }}/android-sdk/cmdline-tools/latest/bin:${{ env.PATH }}
      run: |
        wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O cmdline-tools.zip
        unzip cmdline-tools.zip -d $HOME
        mkdir -p $HOME/android-sdk/cmdline-tools
        mv $HOME/cmdline-tools $HOME/android-sdk/cmdline-tools/latest

        export ANDROID_SDK_ROOT=$HOME/android-sdk
        export ANDROID_HOME=$HOME/android-sdk
        export PATH=$PATH:$HOME/android-sdk/emulator:$HOME/android-sdk/platform-tools:$HOME/android-sdk/cmdline-tools/latest/bin
        yes | sdkmanager --licenses
        sdkmanager "platform-tools" "emulator" "system-images;android-34;default;x86_64"

        # Проверяем, что system-image реально скачан
        ls -la $ANDROID_SDK_ROOT/system-images/android-34/default/x86_64/

        # Добавляем platform-tools в PATH для всех следующих шагов
        echo "$HOME/android-sdk/platform-tools" >> $GITHUB_PATH

    - name: 🔍 Проверка наличия adb в PATH (phone)
      run: |
        echo "PATH: $PATH"
        which adb
        adb version

    - name: 🎭 Install Maestro
      run: |
        curl -Ls "https://get.maestro.mobile.dev" | bash
        export PATH="$PATH":"$HOME/.maestro/bin"
        echo "n" | maestro --version

    - name: 📱 Start Android Emulator and Install APK (Phone)
      shell: bash
      env:
        ANDROID_SDK_ROOT: ${{ runner.home }}/android-sdk
        ANDROID_HOME: ${{ runner.home }}/android-sdk
        PATH: ${{ runner.home }}/android-sdk/emulator:${{ runner.home }}/android-sdk/platform-tools:${{ runner.home }}/android-sdk/cmdline-tools/latest/bin:${{ env.PATH }}
      run: |
        set -e
        echo "SDK root: $ANDROID_SDK_ROOT"
        echo "PATH: $PATH"
        ls -la $ANDROID_SDK_ROOT/system-images/android-34/default/x86_64/
        rm -rf ~/.android/avd/*

        # Создание AVD, если не существует
        if ! avdmanager list avd | grep -q "ci_nexus5"; then
          echo "no" | avdmanager create avd --name "ci_nexus5" --package "system-images;android-34;default;x86_64" --device "Nexus 5"
        fi

        # Запуск эмулятора в фоне
        emulator -avd "ci_nexus5" -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -accel off -no-snapshot -no-metrics &
        EMULATOR_PID=$!

        # Пауза после запуска эмулятора (важно для CI)
        sleep 30

        # Ожидание появления устройства в adb (таймаут 120с)
        emulator_timeout=120
        emulator_elapsed=0
        for i in {1..60}; do
          if adb devices | grep -q "emulator"; then
            echo "✅ Эмулятор появился в adb"
            break
          fi
          if [ $i -eq 60 ]; then
            echo "❌ Не удалось запустить эмулятор за $emulator_timeout секунд"
            kill $EMULATOR_PID 2>/dev/null || true
            exit 1
          fi
          sleep 2
          emulator_elapsed=$((emulator_elapsed + 2))
          if [ $((emulator_elapsed % 30)) -eq 0 ]; then
            echo "⏳ Ожидание эмулятора: $emulator_elapsed секунд из $emulator_timeout..."
          fi
        done

        # Ожидание загрузки Android (sys.boot_completed, таймаут 180с)
        boot_timeout=180
        boot_elapsed=0
        for i in {1..90}; do
          if adb shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
            echo "✅ Android полностью загружен"
            break
          fi
          if [ $i -eq 90 ]; then
            echo "❌ Android не загрузился за $boot_timeout секунд"
            kill $EMULATOR_PID 2>/dev/null || true
            exit 1
          fi
          sleep 2
          boot_elapsed=$((boot_elapsed + 2))
          if [ $((boot_elapsed % 30)) -eq 0 ]; then
            echo "⏳ Ожидание загрузки Android: $boot_elapsed секунд из $boot_timeout..."
          fi
        done

        # Разблокировка экрана
        adb shell input keyevent 82
        adb shell input keyevent 82
        sleep 2

        # Установка APK
        if [ ! -f "releases/debug/latest-debug.apk" ]; then
          echo "❌ Error: latest-debug.apk not found in releases/debug/"
          ls -la releases/debug/
          kill $EMULATOR_PID 2>/dev/null || true
          exit 1
        fi
        adb install releases/debug/latest-debug.apk
        if adb shell pm list packages | grep -q financialsuccess; then
          echo "✅ APK успешно установлен"
        else
          echo "❌ Error: APK installation failed"
          kill $EMULATOR_PID 2>/dev/null || true
          exit 1
        fi

        # ... скриншоты будут сниматься следующим шагом ...

        # Остановка эмулятора
        kill $EMULATOR_PID 2>/dev/null || true
        pkill -f emulator 2>/dev/null || true

    - name: 📸 Run Maestro Screenshots (Phone)
      run: |
        export PATH="$PATH":"$HOME/.maestro/bin"
        mkdir -p screenshots/phone
        
        # Пробуем специфичную конфигурацию для телефона
        local config_file="maestro/screenshots-stable.yaml"
        if [ ! -f "$config_file" ]; then
          config_file="maestro/screenshots.yaml"
        fi
        
        if [ -f "$config_file" ]; then
          echo "Using phone screenshots configuration: $config_file"
          maestro test "$config_file" --format junit --output screenshots/phone/
          echo "📱 Phone screenshots completed!"
        else
          echo "⚠️ Warning: Maestro configuration file not found: $config_file"
          echo "📱 Phone screenshots skipped!"
        fi

    - name: 📤 Upload Phone Screenshots
      uses: actions/upload-artifact@v4
      with:
        name: phone-screenshots-v${{ needs.build-and-test.outputs.version }}
        path: screenshots/phone/
        retention-days: 30

  # Параллельная генерация скриншотов для планшета
  screenshots-tablet:
    runs-on: ubuntu-latest
    needs: build-and-test
    # Убрано условие should_release
    
    steps:
    - name: 📥 Checkout code
      uses: actions/checkout@v4

    - name: 📥 Download APK
      uses: actions/download-artifact@v4
      with:
        name: FinancialSuccess-v${{ needs.build-and-test.outputs.version }}-${{ needs.build-and-test.outputs.date }}

    - name: 🔧 Install Android SDK and platform-tools
      env:
        ANDROID_SDK_ROOT: ${{ runner.home }}/android-sdk
        ANDROID_HOME: ${{ runner.home }}/android-sdk
        PATH: ${{ runner.home }}/android-sdk/emulator:${{ runner.home }}/android-sdk/platform-tools:${{ runner.home }}/android-sdk/cmdline-tools/latest/bin:${{ env.PATH }}
      run: |
        wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O cmdline-tools.zip
        unzip cmdline-tools.zip -d $HOME
        mkdir -p $HOME/android-sdk/cmdline-tools
        mv $HOME/cmdline-tools $HOME/android-sdk/cmdline-tools/latest

        export ANDROID_SDK_ROOT=$HOME/android-sdk
        export ANDROID_HOME=$HOME/android-sdk
        export PATH=$PATH:$HOME/android-sdk/emulator:$HOME/android-sdk/platform-tools:$HOME/android-sdk/cmdline-tools/latest/bin
        yes | sdkmanager --licenses
        sdkmanager "platform-tools" "emulator" "system-images;android-34;default;x86_64"

        # Проверяем, что system-image реально скачан
        ls -la $ANDROID_SDK_ROOT/system-images/android-34/default/x86_64/

        # Добавляем platform-tools в PATH для всех следующих шагов
        echo "$HOME/android-sdk/platform-tools" >> $GITHUB_PATH

    - name: 🔍 Проверка наличия adb в PATH (tablet)
      run: |
        echo "PATH: $PATH"
        which adb
        adb version

    - name: 🎭 Install Maestro
      run: |
        curl -Ls "https://get.maestro.mobile.dev" | bash
        export PATH="$PATH":"$HOME/.maestro/bin"
        echo "n" | maestro --version

    - name: 📱 Start Android Emulator and Install APK (Tablet)
      shell: bash
      env:
        ANDROID_SDK_ROOT: ${{ runner.home }}/android-sdk
        ANDROID_HOME: ${{ runner.home }}/android-sdk
        PATH: ${{ runner.home }}/android-sdk/emulator:${{ runner.home }}/android-sdk/platform-tools:${{ runner.home }}/android-sdk/cmdline-tools/latest/bin:${{ env.PATH }}
      run: |
        set -e
        echo "SDK root: $ANDROID_SDK_ROOT"
        echo "PATH: $PATH"
        ls -la $ANDROID_SDK_ROOT/system-images/android-34/default/x86_64/
        rm -rf ~/.android/avd/*

        # Создание AVD, если не существует
        if ! avdmanager list avd | grep -q "ci_nexus10"; then
          echo "no" | avdmanager create avd --name "ci_nexus10" --package "system-images;android-34;default;x86_64" --device "Nexus 10"
        fi

        # Запуск эмулятора в фоне
        emulator -avd "ci_nexus10" -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -accel off -no-snapshot -no-metrics &
        EMULATOR_PID=$!

        # Пауза после запуска эмулятора (важно для CI)
        sleep 30

        # Ожидание появления устройства в adb (таймаут 120с)
        emulator_timeout=120
        emulator_elapsed=0
        for i in {1..60}; do
          if adb devices | grep -q "emulator"; then
            echo "✅ Эмулятор появился в adb"
            break
          fi
          if [ $i -eq 60 ]; then
            echo "❌ Не удалось запустить эмулятор за $emulator_timeout секунд"
            kill $EMULATOR_PID 2>/dev/null || true
            exit 1
          fi
          sleep 2
          emulator_elapsed=$((emulator_elapsed + 2))
          if [ $((emulator_elapsed % 30)) -eq 0 ]; then
            echo "⏳ Ожидание эмулятора: $emulator_elapsed секунд из $emulator_timeout..."
          fi
        done

        # Ожидание загрузки Android (sys.boot_completed, таймаут 180с)
        boot_timeout=180
        boot_elapsed=0
        for i in {1..90}; do
          if adb shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
            echo "✅ Android полностью загружен"
            break
          fi
          if [ $i -eq 90 ]; then
            echo "❌ Android не загрузился за $boot_timeout секунд"
            kill $EMULATOR_PID 2>/dev/null || true
            exit 1
          fi
          sleep 2
          boot_elapsed=$((boot_elapsed + 2))
          if [ $((boot_elapsed % 30)) -eq 0 ]; then
            echo "⏳ Ожидание загрузки Android: $boot_elapsed секунд из $boot_timeout..."
          fi
        done

        # Разблокировка экрана
        adb shell input keyevent 82
        adb shell input keyevent 82
        sleep 2

        # Установка APK
        if [ ! -f "releases/debug/latest-debug.apk" ]; then
          echo "❌ Error: latest-debug.apk not found in releases/debug/"
          ls -la releases/debug/
          kill $EMULATOR_PID 2>/dev/null || true
          exit 1
        fi
        adb install releases/debug/latest-debug.apk
        if adb shell pm list packages | grep -q financialsuccess; then
          echo "✅ APK успешно установлен"
        else
          echo "❌ Error: APK installation failed"
          kill $EMULATOR_PID 2>/dev/null || true
          exit 1
        fi

        # ... скриншоты будут сниматься следующим шагом ...

        # Остановка эмулятора
        kill $EMULATOR_PID 2>/dev/null || true
        pkill -f emulator 2>/dev/null || true

    - name: 📸 Run Maestro Screenshots (Tablet)
      run: |
        export PATH="$PATH":"$HOME/.maestro/bin"
        mkdir -p screenshots/tablet
        
        # Пробуем специфичную конфигурацию для планшета
        local config_file="maestro/screenshots-tablet.yaml"
        if [ ! -f "$config_file" ]; then
          config_file="maestro/screenshots.yaml"
        fi
        
        if [ -f "$config_file" ]; then
          echo "Using tablet screenshots configuration: $config_file"
          maestro test "$config_file" --format junit --output screenshots/tablet/
          echo "📱 Tablet screenshots completed!"
        else
          echo "⚠️ Warning: Maestro configuration file not found: $config_file"
          echo "📱 Tablet screenshots skipped!"
        fi

    - name: 📤 Upload Tablet Screenshots
      uses: actions/upload-artifact@v4
      with:
        name: tablet-screenshots-v${{ needs.build-and-test.outputs.version }}
        path: screenshots/tablet/
        retention-days: 30

  # Финальная сборка релиза
  create-release:
    runs-on: ubuntu-latest
    needs: [build-and-test, screenshots-phone, screenshots-tablet]
    # Убрано условие should_release
    permissions:
      contents: write

    steps:
    - name: 📥 Checkout code
      uses: actions/checkout@v4

    - name: 📥 Download APK
      uses: actions/download-artifact@v4
      with:
        name: FinancialSuccess-v${{ needs.build-and-test.outputs.version }}-${{ needs.build-and-test.outputs.date }}

    - name: 📥 Download Phone Screenshots
      uses: actions/download-artifact@v4
      with:
        name: phone-screenshots-v${{ needs.build-and-test.outputs.version }}

    - name: 📥 Download Tablet Screenshots
      uses: actions/download-artifact@v4
      with:
        name: tablet-screenshots-v${{ needs.build-and-test.outputs.version }}

    - name: 📸 Prepare screenshots for repository
      run: |
        mkdir -p screenshots/phone screenshots/tablet
        
        # Копируем скриншоты в правильные папки
        if [ -d "screenshots/phone" ]; then
          cp -r screenshots/phone/* screenshots/phone/ 2>/dev/null || true
        fi
        if [ -d "screenshots/tablet" ]; then
          cp -r screenshots/tablet/* screenshots/tablet/ 2>/dev/null || true
        fi
        
        echo "📱 Phone screenshots:"
        ls -la screenshots/phone/ 2>/dev/null || echo "No phone screenshots"
        echo ""
        echo "📱 Tablet screenshots:"
        ls -la screenshots/tablet/ 2>/dev/null || echo "No tablet screenshots"

    - name: 📝 Commit Screenshots to Repository
      run: |
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add screenshots/
        if git diff --staged --quiet; then
          echo "No screenshots to commit"
        else
          git commit -m "📸 Auto-generated screenshots v${{ needs.build-and-test.outputs.version }} - ${{ needs.build-and-test.outputs.date }}
          
          📱 Screenshots:
          - Phone: $(ls screenshots/phone/*.png 2>/dev/null | wc -l) files
          - Tablet: $(ls screenshots/tablet/*.png 2>/dev/null | wc -l) files
          - Total: $(find screenshots/ -name "*.png" 2>/dev/null | wc -l) files"
          git push
        fi

    - name: 🏷️ Create GitHub Release
      uses: softprops/action-gh-release@v1
      with:
        tag_name: v${{ needs.build-and-test.outputs.version }}-${{ needs.build-and-test.outputs.date }}
        name: "🎮 Financial Success v${{ needs.build-and-test.outputs.version }}"
        body_path: RELEASE_NOTES.md
        files: |
          releases/debug/*.apk
          screenshots/phone/*.png
          screenshots/tablet/*.png
        draft: false
        prerelease: false
        generate_release_notes: false

    - name: 📊 Build Summary
      run: |
        echo "🎉 Release completed successfully!"
        echo ""
        echo "� Results:"
        echo "�� Version: ${{ needs.build-and-test.outputs.version }}"
        echo "🔢 Version Code: ${{ needs.build-and-test.outputs.version_code }}"
        echo "📅 Date: ${{ needs.build-and-test.outputs.date }}"
        echo "� APK: releases/debug/FinancialSuccess-v${{ needs.build-and-test.outputs.version }}-${{ needs.build-and-test.outputs.date }}-debug.apk"
        echo "📸 Phone screenshots: screenshots/phone/"
        echo "📸 Tablet screenshots: screenshots/tablet/"
        echo ""
        echo "� Files:"
        ls -la releases/debug/ || echo "No APK files found"
        echo ""
        echo "📸 Phone screenshots:"
        ls -la screenshots/phone/ 2>/dev/null || echo "No phone screenshots"
        echo ""
        echo "📸 Tablet screenshots:"
        ls -la screenshots/tablet/ 2>/dev/null || echo "No tablet screenshots"

Извините, за длинный скрипт, но он так хорош!

У Actions есть множество замечательных особенностей. Во-первых, агенты, да и любой LLM, прекрасно их понимают и умеют писать. Во-вторых, есть целый магазин уже готовых Actions И, кстати, там не только Actions. Например, в нем есть интеграции с трекером Jira, мессенджерами и различными ИИ.

Как пример, в моем листинге используется step checkout@v4, который взят отсюда.

steps:
- name: 📥 Checkout code
  uses: actions/checkout@v4

Он клонирует код в облако github workspaces и далее wokflow может его собирать, переписывать, запускать, делать практически всё, на что хватит вашей безграничной фантазии.

В-третьих, у Actions есть замечательный информативный дашбоард с артефактами, которым можно пользоваться с мобильного телефона.

Большой UI
Большой UI
Мобильный UI
Мобильный UI

Github CLI

Мы получили автоматическую сборку, но получили еще одну точку потенциального сбоя. Теперь может ломаться не только приложение и агенты, но и CI/CD. И разумеется агенты будут заваливать папйплайн регулярно своими замечательными переделками, о которых вы их не просили.

В этот момент появилась идея написать агенту команду не включаться, пока сборка не станет зеленой. Но для этого надо дать возможность управлять Gothub actions. И такая возможность есть. Для этого мы будем использовать инструмент gothub CLI. Достаточно попросить агента установить его себе и далее он получает полную власть.

Например, пользователи вашего замечательного приложения могут заводить свои "хотелки" и баги через WebUI (пример)

А агент сможет обрабатывать их через github cli.

gh issue list

Showing 4 of 4 open issues in RobotAvi/MoneyGame

ID   TITLE                                                                                     LABELS  UPDATED
#35  При нажатии на Активы(вторая кнопка снизу) во время игры, ничего не происходит                    about 4 days ago
#33  При создании персонажа выбираю дату начала, но игра все равно начинается с 1 января 2024          about 4 days ago
#31  При создании персонажа не работает кнопка "Добавить цель"                                         about 4 days ago

Он сможет запустить и далее вывести все запущенные workflow.

gh run list
STATUS  TITLE                     WORKFLOW              BRANCH                EVENT              ID           ELAPSED  AGE
X       Merge branch 'main' o...  🚀 Stable Build &...  main                  push               16317785712  1m53s    about 44 minutes agoX       Merge branch 'main' o...  🚀 Stable Build &...  main                  push               16316849391  2m6s     about 1 hour ago    
X       Merge branch 'main' o...  🚀 Stable Build &...  main                  push               16316340343  5m17s    about 1 hour ago    
X       Merge branch 'main' o...  🚀 Stable Build &...  main                  push               16315742627  2m37s    about 2 hours ago   
X       Merge branch 'main' o...  🚀 Stable Build &...  main                  push               16315344190  10m11s   about 2 hours ago   
X       🔧 Update Android emu...  🚀 Stable Build &...  main                  push               16315013756  10m42s   about 2 hours ago   
X       🔧 Update APK install...  🚀 Stable Build &...  main                  push               16314726566  4m5s     about 3 hours ago   
X       🚀 Stable Build & Rel...  🚀 Stable Build &...  main                  workflow_dispatch  16314541927  6m33s    about 3 hours ago   
X       🚀 Stable Build & Rel...  🚀 Stable Build &...  main                  workflow_dispatch  16314338809  4m39s    about 3 hours ago   
X       Merge pull request #5...  🚀 Stable Build &...  main                  push               16313315708  5m15s    about 4 hours ago   
X       Merge pull request #6...  🚀 Stable Build &...  main                  push               16313305267  2m8s     about 4 hours ago   
X       Исправить видимость т...  🚀 Stable Build &...  cursor/bc-74e564d...  pull_request       16313302064  6m30s    about 4 hours ago   
X       Добавить зарплату, до...  🚀 Stable Build &...  cursor/bc-20aef14...  pull_request       16313293278  4m58s    about 4 hours ago   
X       Update merge and rele...  🚀 Stable Build &...  main                  push               16313285574  1m44s    about 4 hours ago   
X       Merge pull request #5...  🚀 Stable Build &...  main                  push               16278741446  5m32s    about 1 day ago     
X       Merge pull request #5...  🚀 Stable Build &...  main                  push               16278735173  2m19s    about 1 day ago     
X       Fix game start date d...  🚀 Stable Build &...  cursor/fix-game-s...  pull_request       16278689493  4m36s    about 1 day ago     
X       Fix skill selection n...  🚀 Stable Build &...  cursor/fix-skill-...  pull_request       16278669631  5m31s    about 1 day ago     
X       Merge pull request #5...  🚀 Stable Build &...  main                  push               16278054384  8m13s    about 1 day ago     
X       Fix ignored game star...  🚀 Stable Build &...  cursor/fix-ignore...  pull_request       16277568754  5m14s    about 1 day ago 

Посмотреть внутрь исполняемого Workflow.

gh run view 16317785712
  
X main 🚀 Stable Build & Release · 16317785712
Triggered via push about 46 minutes ago

JOBS
✓ build-and-test in 1m30s (ID 46087758362)
X screenshots-tablet in 9s (ID 46087864204)
  ✓ Set up job
  ✓ 📥 Checkout code
  ✓ 📥 Download APK
  X 🔧 Install Android SDK and platform-tools
  - 🔍 Проверка наличия adb в PATH (tablet)
  - 🎭 Install Maestro
  - 📱 Start Android Emulator and Install APK (Tablet)
  - 📸 Run Maestro Screenshots (Tablet)
  - 📤 Upload Tablet Screenshots
  ✓ Post 📥 Checkout code
  ✓ Complete job
X screenshots-phone in 17s (ID 46087864215)
  ✓ Set up job
  ✓ 📥 Checkout code
  ✓ 📥 Download APK
  X 🔧 Install Android SDK and platform-tools
  - 🔍 Проверка наличия adb в PATH (phone)
  - 🎭 Install Maestro
  - 📱 Start Android Emulator and Install APK (Phone)
  - 📸 Run Maestro Screenshots (Phone)
  - 📤 Upload Phone Screenshots
  ✓ Post 📥 Checkout code
  ✓ Complete job
- create-release in 0s (ID 46087884918)

ANNOTATIONS
X Process completed with exit code 127.
screenshots-tablet: .github#29

X Process completed with exit code 127.
screenshots-phone: .github#29


ARTIFACTS
FinancialSuccess-v1.67-2025-07-16_11-07

To see what failed, try: gh run view 16317785712 --log-failed
View this run on GitHub: https://github.com/RobotAvi/MoneyGame/actions/runs/16317785712

Заглянуть в конкретную Job'у внутри workflow.

gh run view 16317785712 --job 46087864204
! both run and job IDs specified; ignoring run ID
  
X main 🚀 Stable Build & Release · 16317785712
Triggered via push about 48 minutes ago

X screenshots-tablet in 9s (ID 46087864204)
  ✓ Set up job
  ✓ 📥 Checkout code
  ✓ 📥 Download APK
  X 🔧 Install Android SDK and platform-tools
  - 🔍 Проверка наличия adb в PATH (tablet)
  - 🎭 Install Maestro
  - 📱 Start Android Emulator and Install APK (Tablet)
  - 📸 Run Maestro Screenshots (Tablet)
  - 📤 Upload Tablet Screenshots
  ✓ Post 📥 Checkout code
  ✓ Complete job

ANNOTATIONS
X Process completed with exit code 127.
screenshots-tablet: .github#29


To see the logs for the failed steps, try: gh run view --log-failed --job=46087864204
View this run on GitHub: https://github.com/RobotAvi/MoneyGame/actions/runs/16317785712

И увидеть логи.

gh run view 16317785712 --job 46087864204 --log 
! both run and job IDs specified; ignoring run ID
screenshots-tablet      Set up job      2025-07-16T11:07:56.9908742Z Current runner version: '2.326.0'
screenshots-tablet      Set up job      2025-07-16T11:07:56.9941910Z ##[group]Runner Image Provisioner
screenshots-tablet      Set up job      2025-07-16T11:07:56.9943260Z Hosted Compute Agent
screenshots-tablet      Set up job      2025-07-16T11:07:56.9944128Z Version: 20250711.363
screenshots-tablet      Set up job      2025-07-16T11:07:56.9945088Z Commit: 6785254374ce925a23743850c1cb91912ce5c14c
screenshots-tablet      Set up job      2025-07-16T11:07:56.9946593Z Build Date: 2025-07-11T20:04:25Z
screenshots-tablet      Set up job      2025-07-16T11:07:56.9947630Z ##[endgroup]
screenshots-tablet      Set up job      2025-07-16T11:07:56.9948542Z ##[group]Operating System
screenshots-tablet      Set up job      2025-07-16T11:07:56.9949596Z Ubuntu
...
screenshots-tablet      🔧 Install Android SDK and platform-tools       2025-07-16T11:08:03.4199712Z ##[error]Process completed with exit code 127.
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.4377242Z Post job cleanup.
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5297561Z [command]/usr/bin/git version
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5332105Z git version 2.50.1
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5375287Z Temporarily overriding HOME='/home/runner/work/_temp/e0586d2a-154f-42d8-8f82-3c3463313645' before making global git config changes
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5376830Z Adding repository directory to the temporary git global config as a safe directory
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5381847Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/MoneyGame/MoneyGame
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5416858Z [command]/usr/bin/git config --local --name-only --get-regexp 
core\.sshCommand
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5448838Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5672398Z [command]/usr/bin/git config --local --name-only --get-regexp 
http\.https\:\/\/github\.com\/\.extraheader
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5693891Z http.https://github.com/.extraheader
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5705901Z [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader
screenshots-tablet      Post 📥 Checkout code   2025-07-16T11:08:03.5735076Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
screenshots-tablet      Complete job    2025-07-16T11:08:03.6053273Z Cleaning up orphan processes

Теперь, мы можем написать агенту не подключаться, пока сборка не станет зеленой. И это даже сработает в 8 из 10 случаев. Но, конечно, же придется иногда поработать самим. Для этого нам потребуется IDE, которая тоже есть в Github.

Github CodeSpaces

Мы посмотрели агентов Cursor и собрали на нем приложение. Запустили CI/CD на actions и дали агентам инструмент отслеживания через github cli. И всё это с мобильного телефона, в перерывах на кофе.

Но вайбкодинг когда-то заканчивается, агенты начинают плохо работать. Приходиться нырять в код и звать на помощь CodeSpaces.

CodeSpaces запускается из второй вкладки, которая выпадает при нажатии на Code.

CodeSpaces - это IDE с терминалом и Github Copilot (бесплатный Copilot быстро кончается, но в маркете есть много альтернатив).

Увы, но пользоваться CodeSpace с телефона практически невозможно.

Так что ноутбуки нам еще понадобятся. Легкого кодинга!

Теги:
Хабы:
+1
Комментарии3

Публикации

Ближайшие события