TypeSafe Computer Use: Betaalbare Automatisering voor macOS

Voorbeeldcommando: clicker "go to techcrunch and take me to the checkout page for the cheapest tickets to their next upcoming event" --act

Waarom

Computergebruik via frontier-modellen is krachtig maar duur: elke stap verstuurt een screenshot en er wordt enkele seconden gewacht op een plan. De meeste stappen hebben echter geen complex plan nodig. Ze vereisen slechts één keuze uit een korte lijst, die snel en goedkoop gemaakt moet worden, met een betrouwbaarheidsscore (confidence number) waarop gefilterd kan worden.

TypeSafe biedt precies dat: een beslissingsmodel dat antwoordt met een keuze uit maximaal 255 opties, inclusief een volledige waarschijnlijkheidsverdeling en een gekalibreerde betrouwbaarheid, binnen enkele honderden milliseconden en met gratis output-tokens. Dit project is een computer-use loop die hieromheen is gebouwd.

Vergelijking: TypeSafe vs. Claude Opus 5

Gemeten op basis van hetzelfde screenshot en doel, waarbij één beslissing wordt genomen:

KenmerkTypeSafe (jev)Claude Opus 5 (kaal screenshot)Multiplier
Input tokens4.8824.785Gelijk
Kosten per beslissing$0,0002$0,032155x goedkoper
Kosten per beslissing (realistische loop met historie)$0,0002$0,035 tot $0,08170x tot 390x goedkoper
Kosten per taak van 12 stappen$0,003$0,40 tot $0,90130x tot 300x goedkoper
Model latentie0,13 tot 0,38 s5,2 s14x tot 40x sneller
End-to-end stap (incl. capture en OCR)ca. 1,5 sca. 5,5 s3,7x sneller

Belangrijke kanttekening: Het grote model kon eventdata direct uit de pixels lezen en onafhankelijk vergelijken. De classifier had hiervoor de hieronder beschreven datum-parsing nodig. Elke vorm van redenering die een frontier-model gratis uitvoert, moet hier worden herbouwd als deterministische status.

Installatie

Vereisten: macOS 14 of nieuwer, Python 3.12 of nieuwer, uv.

git clone https://github.com/awlevin/typesafe-computer-use
cd typesafe-computer-use
uv sync
cp .env.example .env     # vul de sleutels in

Omgevingsvariabelen

VariabeleVereistDoel
TYPESAFEAPIKEYJaVoor elke beslissing
ANTHROPICAPIKEYNeetype_text, door de writer voorgestelde URL's en het uiteindelijke antwoord
CLICKER_EMAILNeeMaakt de type_email actie mogelijk
CLICKER_BROWSERNeeStandaard is Google Chrome
CLICKERWRITERMODELNeeStandaard is claude-haiku-4-5
CLICKERANSWERMODELNeeLeest het laatste scherm voor het definitieve antwoord; standaard claude-sonnet-5

Verleen uw terminal rechten voor Schermopname (Screen Recording) en Toegankelijkheid (Accessibility) in Systeeminstellingen > Privacy & Beveiliging. Zonder schermopname zijn captures slechts achtergronden; zonder toegankelijkheid worden synthetische klikken genegeerd en weigert --act te starten.

Gebruik

uv run clicker "open the Playground"                 # dry run: één stap, print wat er gedaan zou worden
uv run clicker "open the Playground" --act           # bestuurt de machine, tot maximaal 100 stappen
uv run clicker "log in" --act --steps 20 --delay 3   # langere en tragere uitvoering
uv run clicker-inspect "any goal"                    # 3-2-1, capture, opent geannoteerd scherm + payload

Maak de terminal eerst leeg. Deze is zichtbaar op het scherm, dus de tekst ervan dient als OCR-input.

Een live run stoppen

Gebruik Ctrl-C wanneer de terminal focus heeft, of sleep de muis abrupt naar de linkerbovenhoek van het scherm vanuit elke app. De loop stopt ook automatisch bij voltooiing (done), wanneer er geen acties meer zijn (none), bij een betrouwbaarheid onder --min-confidence (0.4), na twee opeenvolgende no-ops, of bij het bereiken van --steps.

Het antwoord

Wanneer de loop stopt, leest de 'writer' het scherm waarop hij stopte en print het resultaat: de informatie waar het doel om vroeg, of de huidige status en de volgende stap als het scherm het antwoord niet bevat. Een dry run die zou hebben geacteerd, en een afgebroken run, printten geen antwoord.

Hoe een stap werkt

Het proces volgt deze stroom:

  1. Inputfase:
  • screencapture → Vision OCR → regels samenvoegen tot blokken → regels die het doel echoën verwijderen.
  • accessibility → actieve elementen (rol, label, frame), gesnoeid tot het zichtbare display; gelabelde klikbare elementen die zijn gesnoeid worden bewaard als off-screen controls.
  • accessibility → gefocust veld (rol, label, placeholder, waarde, frame).
  • AppleScript → voorste app en pid, actieve tab URL.
  • klok → lokale datum en tijd.
  • dates.py → voegt "gedateerd 2026-10-13 (over 27 dagen)" toe aan elk blok met een datum, en "nabij een regel gedateerd..." aan buren.
  1. Beslissingsfase:

Er wordt één TypeSafe-verzoek gestuurd met drie keuzes (four met off-screen controls):

  • Kind: clickitem | usebrowser | type_text | scroll ...
  • Item: Welk item (alleen bij click_item).
  • Site: Welke website (alleen bij use_browser).
  • Offscreen: Welke verborgen controle (alleen bij press_offscreen).
  1. Uitvoeringsfase:

Deterministische actie → wachten → volgende stap.

Items bevatten informatie over hun bron: ocr voor een tekstblok, ax voor een door de app gedeclareerde controle, en ax+ocr wanneer beide hetzelfde vonden. Een ax item wordt gelezen als button 'Share' (top-right), zodat de classifier een echte controle kan onderscheiden van een regel tekst.

Door de beslissing op te splitsen in drie vragen wordt schermruis buiten de actiekeuze gehouden. Overlappende opties leiden tot twijfel in de betrouwbaarheidsscore; daarom is het essentieel dat de actieset wederzijds uitsluitend is.

OCR-kosten

Vision OCR vormt ongeveer twee derde van een stap en rekent af per hoeveelheid tekst in plaats van pixels. Besparingen worden dus behaald door minder van het scherm te lezen.

Crop (Bijsnijden)

Elke stap leest het voorste venster met een marge van 8 pt, plus de menubalk boven dezelfde kolommen, begrensd door het display. Tekst op het bureaublad en in achtergrondvensters wordt als ruis beschouwd. De prijs hiervoor is dat de klok en menu-extra's rechts van het venster niet worden gelezen (deze blijven echter klikbaar via de accessibility tree).

Hergebruik (Reuse)

De capture wordt vergeleken met de vorige op 1/8 schaal in tiles van 256 px. Ongewijzigde tiles behouden hun vorige regels. Gewijzigde tiles worden geclusterd in rechthoeken die apart worden gelezen. Bij meer dan 60% gewijzigde tiles, of bij een app-switch/vensterverplaatsing, wordt de gehele regio opnieuw gelezen.

De timing-regel geeft aan hoeveel er is gelezen: ocr 0.31s (22% of screen, 2 rects). Een replay (--image) leest altijd het hele beeld.

Toegankelijkheidsboom (Accessibility Tree)

OCR kan geen iconen zien. De accessibility tree kan dat wel. Voor elke stap wordt het voorste proces doorzocht op gelabelde, on-screen controls. De dekking varieert per app (bijv. Finder 100%, Chrome 88%, Spotify 0%). AX is dus een aanvullende bron, geen vervanging.

Labels worden gehaald uit AXDescription (web/Electron), AXTitle (AppKit), of een korte AXValue.

Omdat frames onbetrouwbaar kunnen zijn, wordt de boom streng gesnoeid:

  • Sla subtrees over wiens frame buiten het display valt.
  • Sla nodes over die smaller of korter zijn dan 4 pt.
  • Sla AXMenu subtrees over (duizenden items achter een gesloten menu).
  • Sla naamloze AXGroup layout-boxen over.
  • Stop bij 4000 nodes of 0,6 s.

Buiten-scherm bedieningselementen (Off-screen controls)

AXPress heeft geen zichtbaar element nodig. Sommige apps (zoals Notes of Chromium) kunnen klikken afleveren aan links die buiten het zichtbare viewport vallen of in een verborgen Dock. De walk bewaart deze gelabelde, klikbare nodes en biedt ze aan als een aparte lijst via de press_offscreen actie.

Actieruimte (Action Space)

SleutelActie
click_itemDruk op het element via de accessibility tree (indien beschikbaar), anders een muisklik in het centrum van de box.
press_offscreenGebruik AXPress op een gelabelde controle die de app exposeert maar niet toont.
use_browserGa naar de browser. none brengt de huidige pagina naar voren, een catalogussleutel opent een specifieke URL, anders een door de writer voorgestelde URL.
type_textDe writer stelt de tekst samen; deze wordt via de accessibility tree geplaatst, met toetsaanslagen als fallback. Een TypeSafe Noul controleert daarna de waarde.
type_emailVult $CLICKER_EMAIL in op dezelfde manier; alleen mogelijk als een tekstveld gefocust is.
pressenter, pressescapeToetsenbordacties.
scrolldown, scrollupScrollt 10 regels na het positioneren van de cursor over het voorste venster.
waitWachten tot het scherm is geladen.
done, noneStop de loop.

Herkomst van vrije tekst

De classifier genereert zelf nooit tekst. Het schrijvmodel (writer) wordt op drie momenten ingezet:

  1. type_text: Ontvangt het doel, recente acties, label en placeholder van het veld, en OCR-regels in de buurt. Retourneert {fill, text}. Bij wachtwoordvelden wordt fill: false teruggegeven en er wordt niets getypt.
  2. use_browser met site: other: Ontvangt het doel en retourneert {ok, url}. Alleen schone HTTPS-URL's worden geaccepteerd.
  3. Het definitieve antwoord: Wanneer de loop stopt, ontvangt het model het doel, alle acties, de stop-reden, de tekst van het laatste scherm en de capture zelf. Het retourneert {achieved, answer}. Voor deze stap wordt CLICKERANSWERMODEL gebruikt (een sterker model).

Wachtwoorden worden nooit getypt; maak gebruik van de wachtwoordmanager van de browser of SSO-knoppen.

Uitvoeringsmap (Run Folder)

Elke run schrijft naar runs/<timestamp>/ voor offline debugging:

  • run.log, run.json: Volledige log van doel, uitkomst, antwoord, acties en timing.
  • answer-raw.png: De capture waarvan het antwoord is gelezen.
  • step-NNN-raw.png: De ruwe capture van de stap.
  • step-NNN.png: Geannoteerd beeld (Items in blauw, accessibility in oranje, gekozen actie in rood, gefocust veld in groen).
  • step-NNN-payload.txt: Exacte state en criteria verzonden naar TypeSafe.
  • step-NNN-answers.json: Alle waarschijnlijkheden die de classifier retourneerde.

Een voorbeeld van een timing-regel: timing: capture 0.31s screenshot 0.28s app 0.01s window 0.02s field 0.01s url 0.01s ocr 0.31s (22% of screen) ax 0.06s decide 0.21s act 0.05s total 0.95s

Replay

Een opgeslagen capture kan live worden nagespeeld zonder het scherm aan te raken: uv run clicker "same goal" --image runs/<ts>/step-003-raw.png --app "Google Chrome" --url "https://example.com/"

Projectstructuur

typesafecomputeruse/

  • macos.py: Platform-adapter voor Quartz, AX en AppleScript.
  • perception.py: Capture, OCR, regio-beheer, cache en merging.
  • dates.py: Datum-parsing en hints.
  • decide.py: State, criteria en TypeSafe-verzoeken.
  • writer.py: Schrijvmodel, URL-validatie en finale antwoorden.
  • actions.py: Handlers voor elke actie.
  • runner.py: De stap-loop, stop-regels en overdracht naar het antwoord.
  • report.py: Logging en geannoteerde screenshots.
  • timing.py: Stopwatches en samenvattingen.
  • cli.py: clicker en clicker-inspect commando's.
  • tests/: Logica-tests (datum, merging, tree walk, etc.).

Een Linux-port zou macos.py vervangen door xdotool en AT-SPI, en Vision OCR vervangen door PaddleOCR of RapidOCR.

Bekende beperkingen

  • OCR ziet alleen tekst en de accessibility tree is afhankelijk van de app; icon-only knoppen in terminals, canvases of Spotify zijn onzichtbaar.
  • Twee identieke labels leiden tot een split in de stemmen (waarschijnlijkheid).
  • Alleen het hoofdscherm wordt gecaptureerd.
  • Handmatig gebruik van de machine tijdens een --act run verstoort de focus en cursor.
  • De site-catalogus is bewust klein; de writer handelt de rest af.

Ontwikkeling

uv run ruff check . && uv run ruff format --check .
uv run pytest -q

CI runs op macOS. Zie CONTRIBUTING.md.

Licentie: MIT