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

от автора

Меня зовут Авенир Воронов и я отвечаю за Лабораторию инноваций в компании «ДАР». В этой статье я расскажу про опыт работы с агентами курсора и великолепный 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 с телефона практически невозможно.

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


ссылка на оригинал статьи https://habr.com/ru/articles/931296/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *