Меня зовут Авенир Воронов и я отвечаю за Лабораторию инноваций в компании «ДАР». В этой статье я расскажу про опыт работы с агентами курсора и великолепный Github Cli, который сэкономил кучу времени.
Вступление
Лето. Время отпусков. Делать нечего.
Можно залипать в рилсах или сериальчиках на пляже, но зачем, если можно создать что-то прикольное с мобильного через агентов. Начнем с самого простого (стоит 20 USD в месяц или 3 тыс руб в год, если немного поискать).
Итак создаем репозиторий (в моем случае такой) или берем готовый. Даем задачу агенту создать игру.
Пока агент делает игру немного поговорим про UI. Агент изначально работает на Claude Sonnet. Когда токены заканчиваются, предлагает перейти на Auto режим.
Агенты могут работать параллельно. Я рекомендую вести древовидные записи задач для агентов, чтобы самому не потеряться. Иначе уже при запуске 3-4 агентов можно забыть с кем и о чем договаривался. Агенты любят:
-
Неправильно выбирать решение
-
Не доделывать работу до конца
-
Не запускать код у себя перед тем, как передать в тестирование
-
Запускать код у себя, но не передавать в тестирование
-
Проявлять излишнюю инициативу
-
Не проявлять инициативу и просить у вас подтверждение на каждое действиею

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

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

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

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

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

Переходим к дизайну и просим агентов добавить пустые картинки в проект и написать промпты для Midjourney для каждой картинки, с указанием размеров. Готовые промпты и адреса картинок сгружаем в md файл.
Пересылаем промпты в свой генератор и сохраняем в игру.
Агент довольно хорошо «обошелся» со скроллами. Вертикальные прокрутки поставил на все меню, а горизонтальные на подменю (выбор профессии, мечты, навыков и т. д.)
Практически все идеи по кастомизации исходили от агента.
В частности, оценка навыков в рублях и статистика продолжительности жизни были сгенерированы на основе поиска статистики в сети.
Текст на плашках изначально выходил за края. Но достаточно было указать на этот факт, чтобы агент всё исправил.
Интерфейс основного экрана получился практически с первого раза. Все доработки совершал сам агент без необходимости открывать верстку.
На этом этапе самым сложным было объяснить агентам, что такое игровой кубик. Нейросети не понимали принципа расположения точек.
Вариант с генерацией трехмерных кубиков через mesh.ai тоже не увенчался успехом. Кубики выходили странными, точки плыли по граням.
Мы с агентом попробовали написать код для трехмерного куба и натянуть текстуру через питон, но в итоге «сошлись» на простом скрипте для блендера. Cursor сгенерировал скрипт сразу хорошо.
К этому моменту проект уже разросся, и я попросил описать архитектуру, чтобы понимать, что вообще происходит. Расписать попросил по C4. И вот что получилось:
Первый слой из одного квадрата.
Пользователь взаимодействует с приложением через графический интерфейс.
Второй слой агент описал одним контейнером с 4 слоями. Вполне логично для такого простого приложения.
UI слой: Активности (MainActivity, GameActivity, CharacterCreationActivity, ProfessionSelectionActivity, RulesActivity) и layout-файлы.
Логика игры: GameManager, модели (Player, GameState, Card, Asset и др.), обработка событий, расчёты.
Данные: GameDataManager (предоставляет профессии, мечты и др.), хранение состояния в памяти (Parcelable)
Ресурсы: Изображения, иконки, стили, строки, layout-описания
На третьем слое агент нарисовал 4 компонента.
1. 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 есть замечательный информативный дашбоард с артефактами, которым можно пользоваться с мобильного телефона.
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/
Добавить комментарий