Skip to main content
Glama
README.md
# TapScout v2

Kendi mobil uygulama build'lerini, MCP'ye bağlı bir ajana **gerçek bir Android emülatöründe ve iOS
Simulator'da yereldeki bir cihaz gibi** kullandırmak için. Cihazlar ücretsiz GitHub Actions
runner'larında (veya bu bilgisayardaki emülatörde) açılır. Hangi testin yapılacağına ve neyin hata
olduğuna **bağlı ajan karar verir**. TapScout yalnızca komutları uygular, ekranı gösterir ve kanıtı
saklar.

- Model API'si yok, API anahtarı yok, ücretli servis yok. Supabase ve Vercel kaldırıldı. Veriler
  `.data/` altında düz dosyalar olarak durur.
- Web arayüzü ve MCP sunucusu yerelde çalışır (`127.0.0.1`).
- TapScout v1'in (Nebius hackathon) GitHub Actions cihaz altyapısı korundu: KVM'li Android
  emülatörü, macOS iOS Simulator, Appium (UiAutomator2 / XCUITest), önceden derlenmiş WDA, OIDC
  kimliği.

## Mimari

```
MCP istemcisi (Claude Code, Claude Desktop…)
   │ stdio köprüsü veya http://127.0.0.1:4780/mcp
   ▼
Hub (yerel)  ── web arayüzü :4780 (canlı yayın, ajanın gördükleri, bulgular, dosyalar)
   │            dosya deposu .data/ · GitHub dispatch/cancel (gh token)
   │ runner ağ geçidi :4781 ── yalnızca bu port dışarı açılır (cloudflared quick tunnel)
   ▲ WebSocket (runner bağlantıyı dışarıdan başlatır) + build indirme + kanıt yükleme
Runner (GitHub Actions job veya yerel süreç)
   OIDC ile kimlik → build'i hub'dan indir, SHA-256 doğrula → Appium oturumu → komut bekle
   Android: emülatör + UiAutomator2 · iOS: Simulator + XCUITest/WDA
```

| Paket | Görev |
|---|---|
| `packages/hub` | MCP sunucusu, yerel web arayüzü, oturum yönetimi, runner ağ geçidi, tünel, rapor |
| `packages/runner` | Cihaz tarafı: Appium kontrolü, canlı kare akışı, kare yakalama, kayıt, log ve çökme toplama |
| `packages/shared` | Hub ve runner protokolü, koordinat dönüşümleri, UI ağacı normalizasyonu |
| `apps/sample-app` | FieldNotes: bilinen hatalar içeren örnek Expo uygulaması (doğrulama için) |
| `.github/workflows/device-session.yml` | Bir cihaz oturumu (Android veya iOS) |
| `.github/workflows/build-sample.yml` | Örnek uygulamanın APK'sı ve iOS Simulator `.app.zip`'i (artifact) |

## Kurulum

Gerekenler: Node.js 22.13+, pnpm 10, GitHub CLI (`gh auth login`). Yerel Android için Android
SDK (adb ve emülatör) gerekir.

```bash
pnpm install          # paketleri derler (prepare)
pnpm setup:local      # .tools/ altına Appium + UiAutomator2 (+ macOS'ta XCUITest) ve cloudflared kurar
pnpm hub              # hub: http://127.0.0.1:4780
```

GitHub oturumları için repo `main` dalında `device-session.yml` workflow'unu içermelidir. Hub,
workflow'u `gh auth token` ile tetikler. Repo varsayılan olarak `origin` uzak deposundan okunur
(`TAPSCOUT_GITHUB_REPO` ile değiştirilebilir). Diğer ayarlar için [`.env.example`](.env.example)
dosyasına bakın.

### MCP bağlantısı

- **Claude Code (bu repo içinde):** `.mcp.json` hazır gelir (stdio köprüsü). Hub çalışmıyorsa
  köprü onu arka planda başlatır (log: `.data/hub.log`).
- **Claude Code (HTTP, hub açıkken):**
  `claude mcp add --transport http tapscout http://127.0.0.1:4780/mcp`
- **Claude Desktop:** `claude_desktop_config.json` içine
  `{"mcpServers":{"tapscout":{"command":"node","args":["<repo>/packages/hub/dist/mcp-stdio.js"]}}}`

Oturumlar istemciden bağımsız olarak hub'da yaşar. İstemci yeniden başlasa da oturum sürer.
`/mcp__tapscout__test_app` prompt'u önerilen test döngüsünü başlatır.

## MCP araçları

| Grup | Araçlar |
|---|---|
| Oturum | `session_start`, `session_status` (en fazla 50 sn bekleyebilir), `session_list`, `session_stop`, `session_report` |
| Gözlem | `device_screenshot` (isteğe bağlı element kutuları ve ızgara), `device_zoom` (görünüm birimleriyle etiketli ızgara), `device_ui_tree` (compact, json veya xml) |
| Dokunma | `device_tap` (tek veya çift; `alert:<düğme>` ile diyalog yanıtı), `device_long_press`, `device_swipe`, `device_drag`, `device_scroll`, `device_pinch` |
| Metin | `device_type` (append veya replace, submit, secret), `device_key` (Android tuşları; iOS'ta temel tuşlar), `device_keyboard` (status veya hide) |
| Uygulama | `app_restart` (veri silmeyle ya da silmeden), `app_control` (launch, terminate, background, state, open_url, clear_data), `device_rotate`, `device_wait` (idle, text, text_gone) |
| Zaman | `device_capture_frames` (bir eylem etrafında zaman damgalı kareler; kontak sayfası), `device_recent_frames` (canlı tampon), `device_recording` (MP4) |
| Kanıt | `device_logs`, `device_crashes`, `evidence_capture`, `finding_record` |

**Koordinatlar:** bütün x, y ve bölge değerleri *görünüm birimidir*. Görünüm birimi,
`device_screenshot` görüntüsünün pikselidir (örneğin 576×1280). Hub bunu Android'de px'e, iOS'ta
pt'ye çevirir. Element hedefleri de kullanılabilir: `e12`, `id:login_button`, `text:Kaydet`.
Bir eylemden sonra ref'ler eski kalırsa hub elementi yeni ağaçta kimliğine göre yeniden bulur.

## İstemciye bağlı kısıtlar

- **Model video görmez.** MCP'de video içerik tipi yok. Ajan yalnızca araçların döndürdüğü
  durağan görüntüleri görür. Animasyon ve geçici hatalar için `device_capture_frames` saniye
  etiketli bir kontak sayfası döndürür. `device_recent_frames` yakın geçmişi yaklaşık 30 sn'lik
  canlı tampondan verir. Canlı MJPEG yayını ve MP4 kayıtları **yalnızca insan içindir**. Web
  arayüzü "Canlı yayın" ile "Ajanın gördükleri" panellerini bu yüzden ayrı gösterir; ikinci panel
  modele gerçekten gönderilen her görüntüyü listeler.
- **Görüntü maliyeti:** 576×1280 bir ekran yaklaşık 966 token tutar (`⌈w/28⌉·⌈h/28⌉`). Claude
  Code'da bir MCP sonucu varsayılan olarak 25k token ile sınırlıdır (`MAX_MCP_OUTPUT_TOKENS`).
  Eylemlerde `observe: "none" | "tree"` ile görüntü almadan devam edilebilir.
- **Zaman aşımı:** Claude Code'un HTTP MCP istekleri varsayılan olarak 60 sn'de kesilir; stdio'da
  istek başına sınır yoktur. Bu yüzden her araç 55 sn'nin altında kalır. Oturum açılışı (GitHub'da
  3–8 dk) asenkron ilerler ve `session_status` ile takip edilir. Claude Desktop'ın sınırları
  belgelenmemiştir.
- **Sunucu yönergeleri** 4096 karakter sınırının altındadır. Prompt'lar Claude Code'da slash
  komutu olarak görünür.
- **claude.ai web bağlayıcıları** localhost'a erişemez; MCP bilerek dışarı açılmaz.
- **Koordinatlar görüntüden tahmin edilir.** Küçük hedeflerde element ref'leri, `annotate` ve
  `device_zoom` ızgarası kullanılmalıdır.

## Güvenlik

- **Runner kimliği:** GitHub OIDC tokenı GitHub JWKS'e göre doğrulanır (imza, issuer, audience
  `tapscout-v2`, süre). Ayrıca repo, `device-session.yml@refs/heads/main` ve `workflow_dispatch`
  koşulları aranır. Bir oturuma bağlanan ilk run'dan farklı bir run reddedilir. Yerel runner tek
  kullanımlık rastgele token kullanır.
- **Oturum tokenı:** Hub her runner'a kendi imzaladığı bir oturum tokenı verir (HS256, sır
  `.data/hub-secret` içinde). Yeniden bağlanma, build indirme ve kanıt yükleme yalnızca bu tokenla
  yapılır.
- **Dış erişim:** Tünel yalnızca ağ geçidi portunu (`/healthz`, `/runner/*`) açar. Web arayüzü,
  API ve MCP `127.0.0.1` üzerindedir ve Host/Origin denetimiyle DNS rebinding'e karşı korunur.
- **Herkese açık repo:** Workflow logları herkese açıktır. Runner ekran içeriği, yazılan metin veya
  token yazmaz. Build ve kanıtlar GitHub'a yüklenmez; artifact yükleme isteğe bağlıdır
  (`upload_artifacts`).

## Bağlantı ve temizlik

- Runner kopunca tekrar bağlanır (üstel bekleme; tolerans varsayılan 10 dk). Bu sürede komutlar
  "disconnected" hatası döner. Hub sessiz bir bağlantıyı 45 sn içinde düşürür.
- Boştaki oturum 20 dk sonra durur (`TAPSCOUT_IDLE_MINUTES`). Her oturumun bir azami süresi de
  vardır.
- `session_stop` önce runner'dan logları ve varsa kaydı yüklemesini ister, Appium'u kapatır ve job
  kendiliğinden biter. Bitmeyen GitHub run'ı iptal edilir. Hub yeniden başlarsa yarım kalan
  oturumlar bir süre beklenir; runner dönmezse oturum kapatılır ve run iptal edilir.
- Appium oturumu çökerse runner, uygulama verisini koruyarak yeni bir oturum açar ve komutu yeniden
  dener.

## Sınırlar

- iOS yalnızca **iOS Simulator**'da çalışır (`xcodebuild -sdk iphonesimulator` ile üretilmiş
  `.app`'in zip'i). Yerel iOS için macOS gerekir.
- Android emülatörü x86_64'tür. arm64 APK'lar çeviriyle çalışır; yalnızca 32 bit APK'lar çalışmaz.
- Quick tunnel adresi hub her yeniden başladığında değişir. Sabit adres için
  `TAPSCOUT_PUBLIC_URL` kullanılır.
- Android `screenrecord` en fazla 180 sn kaydeder.

## Doğrulama durumu

- **Yerel Android (Windows, Android 16 emülatörü), FieldNotes `seeded` build'i:** Runner AVD'yi
  boot etti, Appium'u `.tools/`'tan başlattı ve build'i kurdu. MCP araçlarının hepsi gerçek
  cihazda çalıştırıldı: dokunma (ref, id ve text), Türkçe ve emoji ile yazma, klavye, kaydırma,
  sürükleme, uzun basma, pinch, döndürme, arka plan, `app_restart`, bir dokunmanın etrafında
  kare yakalama (geçiş animasyonu görüldü), canlı tampon, MP4 kayıt, loglar, çökme sorgusu, kanıt
  ve bulgu, rapor, durdurma. Kapanışta loglar yüklendi ve Appium kapandı. Ajan döngüsüyle
  planlanmış "profil düzenlemesi yeniden başlatınca kayboluyor" hatası yeniden üretildi.
- **GitHub Actions (Android ve iOS):** Workflow'lar CI'da actionlint'ten geçiyor ve örnek
  uygulamanın her iki platform için build'i GitHub'da başarıyla alındı. Tünel üzerinden uçtan uca
  oturum henüz çalıştırılmadı: quick tunnel'ı açmak açık izin gerektiriyor.

## Geliştirme

```bash
pnpm check     # biome + tip denetimi + testler (shared, runner, hub; sahte runner ve MCP istemcisiyle uçtan uca)
```

Lisans: [Apache-2.0](LICENSE). Örnek uygulama şablonu © 650 Industries (Expo), MIT.