From 47c80f2bffea15c0e24200700e4caa59653ad47b Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Mon, 28 Sep 2026 16:52:55 +0800 Subject: [PATCH 001/214] =?UTF-8?q?fix:=20byg=20paa=20macos-15,=20aerlige?= =?UTF-8?q?=20forgrunds-loefter,=20fremmed-mac=20som=20=C3=A6gte=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci/release/udgiv-npm: macos-14 -> macos-15 (SCRecordingOutput mangler i 14-SDK, CI rød på main) - README + vejledning: fjern 'nothing comes to the front'; sig at forgrunden gives tilbage - menu-needs-front: 'may be' i stedet for påstået årsag; peg ikke paa en vej der afvises bagfra - brugsscenarier: skærm taget (selv med tilbagegivning) = 'delvist', ikke 'bevist' (MANDAT) - fremmed-mac: samlende port fejler jobbet hvis suite/mutant/scenarier ikke bestod Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 2 +- .github/workflows/fremmed-mac.yml | 22 ++++++++++++++++++++++ .github/workflows/release.yml | 2 +- .github/workflows/udgiv-npm.yml | 2 +- README.md | 5 +++-- mcp-server/index.js | 9 +++++---- test/brugsscenarier.mjs | 5 ++++- 7 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9197a272..762a3738 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: build-and-test: # macOS-koerer. Der findes ingen anden slags: hele produktet er bygget paa # macOS' egne rammevaerker. - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/fremmed-mac.yml b/.github/workflows/fremmed-mac.yml index 7796c39f..9177a133 100644 --- a/.github/workflows/fremmed-mac.yml +++ b/.github/workflows/fremmed-mac.yml @@ -59,6 +59,7 @@ jobs: mcp-server/vendor/cmcp-helper focused || true - name: Hele suiten + id: suite continue-on-error: true run: | export TMPDIR="$RUNNER_TEMP/" @@ -69,6 +70,7 @@ jobs: exit $rc - name: Mutationsbevis for giv-tilbage (proeven tager forgrunden, saa kun her) + id: mutant continue-on-error: true run: bash .github/mutant-giv-tilbage.sh @@ -83,6 +85,7 @@ jobs: mcp-server/vendor/cmcp-helper focused || true - name: Brugsscenarierne i baggrunden + id: bg continue-on-error: true run: CMCP_BRUG=laes,lokal,kamera CMCP_BRUG_RAPPORT="$RUNNER_TEMP/baggrund.json" node test/brugsscenarier.mjs @@ -98,6 +101,7 @@ jobs: sleep 2 - name: Brugsscenarierne i forgrunden + id: fg continue-on-error: true run: CMCP_BRUG=laes,lokal,kamera CMCP_BRUG_TILSTAND=forgrund CMCP_BRUG_TAG_SKAERMEN=1 CMCP_BRUG_RAPPORT="$RUNNER_TEMP/forgrund.json" node test/brugsscenarier.mjs @@ -115,3 +119,21 @@ jobs: ${{ runner.temp }}/baggrund.json ${{ runner.temp }}/forgrund.json ${{ runner.temp }}/diag/ + + # ⛔ 28/9: foer var suite, mutant og de to scenarie-koersler alle + # continue-on-error uden en samlende port - en groen koersel beviste + # intet om proeverne. Nu fejler jobbet, hvis en af dem ikke bestod. + # (Beviserne uploades ovenfor med if: always, saa de findes stadig.) + - name: Porten - en fejlet proeve goer hele jobbet roedt + if: always() + run: | + fail=0 + for par in "Suiten:${{ steps.suite.outcome }}" \ + "Mutationsbeviset:${{ steps.mutant.outcome }}" \ + "Baggrunds-scenarier:${{ steps.bg.outcome }}" \ + "Forgrunds-scenarier:${{ steps.fg.outcome }}"; do + navn="${par%%:*}"; res="${par##*:}" + echo "$navn: $res" + [ "$res" = "success" ] || { echo "⛔ $navn bestod ikke ($res)"; fail=1; } + done + [ "$fail" = "0" ] && echo "Alle fire porte grønne." || exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32b6f897..e2bfcaa9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ permissions: jobs: universal: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/udgiv-npm.yml b/.github/workflows/udgiv-npm.yml index 605da7cc..b96ac775 100644 --- a/.github/workflows/udgiv-npm.yml +++ b/.github/workflows/udgiv-npm.yml @@ -47,7 +47,7 @@ jobs: udgiv: # macOS fordi build-release.sh bygger den universelle Swift-binaer # (arm64 + x86_64) der ligger i pakken. En Linux-koerer kan ikke. - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index e0eb6728..ebe09e0e 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ in the hours you are not watching. - **It does not take over your Mac.** It presses buttons and fills fields in windows that stay behind the one you are in, and it leaves your pointer where - you put it. Nothing gets minimised, nothing comes to the front, nothing steals - what you are typing. + you put it. When an app pulls itself to the front as it opens, the front is + handed straight back and the reply says so - nothing is left stealing what you + are typing. - **It does not pretend.** When something is refused or fails, it says so and says why. The most common complaint about agents driving a computer is that they carry on as if it worked. diff --git a/mcp-server/index.js b/mcp-server/index.js index d0809802..475ff1ec 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -176,10 +176,11 @@ When a step did nothing - the next route (measured 27-28 Sep): - \`computer_press\` said ok and nothing changed (a sidebar in App Store or System Settings): the app ignores presses there from behind. Find the same action in its menus with \`computer_menus\` - System Settings has every page under View. -- A menu item is greyed out, code \`menu-needs-front\`: the app only allows it - while it is in front. Ask the person to bring it forward, then call again. +- A menu item is greyed out, code \`menu-needs-front\`: this may be because the + app only enables it while it is in front - which cannot be done from behind. + Tell the person and let them bring it forward; do not keep pressing it. - The app runs but has no window: \`computer_launch\` with \`background: true\` - asks it to show one, without bringing it forward. + asks it to show one; if the app pulls itself forward the front is handed back. - Nothing is found in an app you just saw: check \`computer_windows\` - the window may have been closed. - Whichever it was, write it down with \`computer_learning\`: the route that @@ -1075,7 +1076,7 @@ async function haandterKald(request) { `so the person would see it happen.\n\n` + `Set \`${MANGLER_FOR_STILLE[name]}\` and call it again. ` + `The event then goes into that app's own queue: the pointer stays where ` + - `the person left it, nothing comes to the front, and it works on a window ` + + `the person left it, this call pulls nothing to the front, and it works on a window ` + `behind the one they are in.\n` + `Use computer_apps or computer_windows if you are unsure of the name.` ); diff --git a/test/brugsscenarier.mjs b/test/brugsscenarier.mjs index 689ce4d7..4559f9d5 100644 --- a/test/brugsscenarier.mjs +++ b/test/brugsscenarier.mjs @@ -484,7 +484,10 @@ export async function koerScenarie(s, { forgrund = false } = {}) { res.status = 'fejlede'; res.bevis = `tog skærmen og gav den ikke tilbage: ${beholdt.map(t => `${t.navn} (${t.hvorfor.slice(0, 80)})`).join('; ')} · ${res.bevis}`; } else if (tog.length) { - res.bevis += ` · ${tog.length}x hentede et program sig selv frem, forgrunden blev givet straks tilbage`; + // MANDAT: at tage skærmen og give den tilbage er stadig at tage skærmen. + // Genopretning er bedre end intet, men den gør ikke et forløb til baggrund. + res.status = 'delvist'; + res.bevis += ` · ${tog.length}x hentede et program sig selv frem (skærmen taget, forgrunden givet straks tilbage) - ikke rent baggrundsforløb`; } } catch (e) { res.status = e instanceof KunForgrund ? 'delvist' : 'fejlede'; From dcf361ab2dc89358bc5e54746ef5aa6a1acc98b0 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Mon, 28 Sep 2026 18:56:11 +0800 Subject: [PATCH 002/214] =?UTF-8?q?vejledning:=20sp=C3=B8rg=20n=C3=A5r=20d?= =?UTF-8?q?u=20er=20blokeret,=20giv=20ikke=20op=20-=20tre=20ting=20er=20me?= =?UTF-8?q?nneskets=20at=20give?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustav 28/9: modellen skal vide den kan alt et menneske kan, og enten finde vejen eller spørge (computer_ask_user) frem for at stoppe. Login/kodeord, afsendelse til et rigtigt menneske, og forgrund er menneskets; aldrig taste kodeord, aldrig omgå en afvisning. Co-Authored-By: Claude Opus 4.8 --- mcp-server/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mcp-server/index.js b/mcp-server/index.js index 475ff1ec..05b562ed 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -194,9 +194,16 @@ What you will be refused, and why: - an unredacted screenshot: that is the person's decision, never the model's - the Computer MCP status icon itself: it is their control surface -When something needs a human, the question waits in the menu bar icon and the -person answers it with Touch ID. You are told the action did not happen; call -it again after they approve. \`computer_pending\` lists what is waiting. +When something needs a human, do not give up - ask. This tool can drive anything +on the Mac a person can reach by hand; three things stay theirs to give: a login +or password, the go-ahead to send a message to a real person, and bringing an app +to the front for something that only works there. When you need one of these, call +\`computer_ask_user\`: the question waits in the menu bar icon and the person answers +with Touch ID and types any secret themselves - you never type a password and never +see it. You are told the action did not happen; call it again after they approve. +\`computer_pending\` lists what is waiting. Handing back "I can't" before you have +asked is the one wrong move; when in doubt whether you may do something, ask whether +to do it - and never work around a refusal. Reading: \`computer_inspect\` answers as text by default. If it says INCOMPLETE or [cut: ...], the answer is PART of the tree - narrow it with \`computer_find\` From 6a5868f846b856f2a88a8ebeb27a2082de01a0da Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Mon, 28 Sep 2026 19:25:29 +0800 Subject: [PATCH 003/214] docs: regenerer npm-README efter forgrunds-loefte-rettelsen (holder claim 45 groen) Co-Authored-By: Claude Opus 5.5 --- mcp-server/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mcp-server/README.md b/mcp-server/README.md index 71f86584..fa68dd91 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -7,8 +7,9 @@ in the hours you are not watching. - **It does not take over your Mac.** It presses buttons and fills fields in windows that stay behind the one you are in, and it leaves your pointer where - you put it. Nothing gets minimised, nothing comes to the front, nothing steals - what you are typing. + you put it. When an app pulls itself to the front as it opens, the front is + handed straight back and the reply says so - nothing is left stealing what you + are typing. - **It does not pretend.** When something is refused or fails, it says so and says why. The most common complaint about agents driving a computer is that they carry on as if it worked. From d1c5cfe8a7c38c0c9a77ce3ba970a4b9609710fa Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Mon, 28 Sep 2026 20:41:51 +0800 Subject: [PATCH 004/214] haerdning M1: programlaasen fejler LUKKET ved infra-fejl (ikke aaben) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fandt af batch-2-hærdningen (29 bekræftede huller): programlaas.js:45 kørte handlingen alligevel ved enhver ikke-EEXIST låsefejl (ENOTDIR/EACCES). To agenter kunne skrive i flæng i samme program - netop det låsen skal forhindre. Nu: kan låsen ikke tages, sker handlingen ikke, med en ærlig grund (infra, ikke contention). Prøve test/laas-fejler-lukket.mjs tvinger ENOTDIR frem; revert af M1 gør den rød. Co-Authored-By: Claude Opus 5.5 --- mcp-server/index.js | 6 ++++-- mcp-server/programlaas.js | 6 +++++- test/laas-fejler-lukket.mjs | 40 +++++++++++++++++++++++++++++++++++++ test/run-all.sh | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 test/laas-fejler-lukket.mjs diff --git a/mcp-server/index.js b/mcp-server/index.js index 05b562ed..97ff0593 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -1288,9 +1288,11 @@ async function haandterKald(request) { return errorResult(`Refused: ${stopgrund}. Nothing was done. Call it again.`); } if (!laast.ok) { - const grund = `another agent is working in ${targetBundleId || 'the foreground app'} right now`; + // laast.grund er sat naar laasen fejlede LUKKET paa en infra-fejl (M1); + // ellers er det ventetiden der udloeb (en anden agent holdt laasen). + const grund = laast.grund || `another agent is working in ${targetBundleId || 'the foreground app'} right now, and it did not finish within a minute`; record({ tool: name, outcome: 'refused', reason: grund }); - return errorResult(`Refused: ${grund}, and it did not finish within a minute. Nothing was done. Try again shortly.`); + return errorResult(`Refused: ${grund}. Nothing was done. Try again shortly.`); } result = laast.vaerdi; } diff --git a/mcp-server/programlaas.js b/mcp-server/programlaas.js index b12aee16..6df83fb2 100644 --- a/mcp-server/programlaas.js +++ b/mcp-server/programlaas.js @@ -42,7 +42,11 @@ export async function medProgramLaas(program, fn, maksVentMs = 60_000) { try { writeSync(fd, String(process.pid)); } finally { closeSync(fd); } break; } catch (err) { - if (err.code !== 'EEXIST') return { ok: true, vaerdi: await fn() }; // kan ikke laase: gaa videre som foer + // ⛔ 28/9 (haerdning M1): FOER kørte vi fn() alligevel her (fail-open) - en + // laase-infra-fejl (ENOTDIR hvis laase-mappen bliver en fil, EACCES m.fl.) + // lod to agenter skrive i flaeng, netop dét laasen skal forhindre. Nu + // fejler laasen LUKKET: kan handlingen ikke serialiseres, sker den ikke. + if (err.code !== 'EEXIST') return { ok: false, grund: `the per-app lock could not be taken (${err.code || 'unknown error'}); an action that cannot be serialised does not happen` }; if (!ejerLever(sti)) { try { unlinkSync(sti); } catch {} continue; } if (Date.now() - start > maksVentMs) return { ok: false }; await sov(40); diff --git a/test/laas-fejler-lukket.mjs b/test/laas-fejler-lukket.mjs new file mode 100644 index 00000000..15332563 --- /dev/null +++ b/test/laas-fejler-lukket.mjs @@ -0,0 +1,40 @@ +// M1 (28/9): programlåsen fejler LUKKET ved en infra-fejl. +// +// ⛔ HVORFOR DEN FINDES +// programlaas.js:45 kørte FØR handlingen alligevel (fail-open), hvis låsen +// ikke kunne tages af en anden grund end "optaget" (EEXIST). En låse-mappe +// der bliver en fil giver ENOTDIR - og så skrev to agenter i flæng i samme +// program, netop det låsen skal forhindre. Prøven tvinger ENOTDIR frem og +// kræver, at handlingen AFVISES (ok:false), ikke udføres. +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const fails = []; +const check = (l, c, d = '') => { console.log(`${c ? 'OK ' : 'DUMP'} ${l}${d ? ' - ' + d : ''}`); if (!c) fails.push(l); }; + +// Gør 'laase'-stien til en FIL, så openSync inde i den giver ENOTDIR (ikke EEXIST). +const base = mkdtempSync(join(tmpdir(), 'cmcp-laas-')); +writeFileSync(join(base, 'laase'), 'jeg er en fil, ikke en mappe'); +process.env.CMCP_STATE_DIR = base; + +// DIR læses ved modul-load fra env, så env SKAL sættes før import. +const { medProgramLaas } = await import('../mcp-server/programlaas.js'); + +let kørt = false; +const r = await medProgramLaas('com.apple.TextEdit', async () => { kørt = true; return 'skulle-ikke-ske'; }); + +check('1 laasen fejler LUKKET (ok:false), ikke aaben', r.ok === false, JSON.stringify(r)); +check('2 handlingen blev IKKE udfoert', kørt === false); +check('3 afslaget siger hvorfor (infra, ikke contention)', /could not be taken|serialised/.test(r.grund || ''), r.grund); + +// Kontrol: en ren mappe låser og kører handlingen som normalt. +const base2 = mkdtempSync(join(tmpdir(), 'cmcp-laas-ok-')); +process.env.CMCP_STATE_DIR = base2; +const { medProgramLaas: laas2 } = await import('../mcp-server/programlaas.js?ren'); +let kørt2 = false; +const r2 = await laas2('com.apple.TextEdit', async () => { kørt2 = true; return 42; }); +check('4 en ren mappe laaser og koerer handlingen', r2.ok === true && r2.vaerdi === 42 && kørt2 === true, JSON.stringify(r2)); + +console.log(fails.length ? `DUMPET: ${fails.length} tjek` : 'Alle tjek bestået.'); +process.exit(fails.length ? 1 : 0); diff --git a/test/run-all.sh b/test/run-all.sh index df3e1ed1..2b36940a 100755 --- a/test/run-all.sh +++ b/test/run-all.sh @@ -92,6 +92,7 @@ run "paastande" "node test/claims.mjs" # og den var dermed ubevist i naesten hver koersel. Nu gaar spoergsmaalet # gennem en attrap, saa den koerer HVER gang og viser ingenting. run "fejl-lukket" "node test/failclosed.mjs" +run "laas fejler lukket" "node test/laas-fejler-lukket.mjs" run "fejlbeskeder" "node test/errors.mjs" run "flere agenter" "node test/concurrent.mjs" From b5d0953d3f1b39d7fbdd73666347298a0580df86 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Mon, 28 Sep 2026 23:44:19 +0800 Subject: [PATCH 005/214] haerdning M4: giv aldrig forgrunden fra et menneske der lige skiftede MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fandt af batch-2-review'et: givTilbage() gav forgrunden tilbage naar maalprogrammet kom frem UANSET nyligt menneske-input - klikkede mennesket selv over i dét program, rev vi det tilbage. Nu gaelder menneske-tjekket i alle grene, og et forgrunds-skift vi lader staa rapporteres aerligt (took_screen:true, gave_back:false) i stedet for et tavst false. Paa en maskine uden menneske (køreren) er adfaerden uaendret, saa giv-tilbage-mutanten verificerer fortsat. Co-Authored-By: Claude Opus 4.8 --- helper/Sources/cmcp-helper/Skaerm.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/helper/Sources/cmcp-helper/Skaerm.swift b/helper/Sources/cmcp-helper/Skaerm.swift index 63b76090..470eece8 100644 --- a/helper/Sources/cmcp-helper/Skaerm.swift +++ b/helper/Sources/cmcp-helper/Skaerm.swift @@ -187,10 +187,19 @@ enum Skaerm { // trykker en tast eller klikker; et program der skubber sig frem, goer // ingen af delene. Uden menneskelig input i 1,5 s var det ikke mennesket. let maal = tilPid() - guard maal != foer.forrestPid, efter.forrestPid == maal || !menneskeRoerteNetop() else { - return ["took_screen": false, + // Maalet er der mennesket allerede var: intet skift at give tilbage. + if maal == foer.forrestPid { return ["took_screen": false] } + // ⛔ 28/9 (haerdning M4): FOER stod `efter.forrestPid == maal || !menneskeRoerteNetop()` + // - naar maalprogrammet kom frem, blev forgrunden givet tilbage UANSET om + // mennesket lige havde klikket/tastet. Klikkede mennesket selv over i netop + // dét program, rev vi det tilbage under haenderne paa dem. Nu gaelder + // menneske-tjekket i ALLE grene: har nogen roert tastatur eller mus lige + // foer, saa lader vi forgrunden staa - og siger aerligt at skaermen skiftede, + // i stedet for et tavst took_screen:false. + if menneskeRoerteNetop() { + return ["took_screen": true, "gave_back": false, "observed": ["frontmost_changed_to": efter.forrestNavn, - "note": "someone pressed a key or clicked just before, so this is taken to be the person switching - left alone"]] + "note": "a person clicked or typed just before, so this is taken to be them and the front was left with \(efter.forrestNavn)"]] } NSRunningApplication(processIdentifier: foer.forrestPid)?.activate(options: []) var tilbage = false From 4f9c98c3cdbb8fdc06b85963114750c6e3cff76d Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 29 Sep 2026 00:01:58 +0800 Subject: [PATCH 006/214] test(mutant): opdater M4-mutanten til den nye give-tilbage-kode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M4-ændringen fjernede den linje den gamle mutant patchede (efter.forrestPid==maal || !menneskeRoerteNetop()), saa mutanten kunne ikke anvendes og 'overlevede' falsk. Ny mutant: faa menneske-tjekket til altid at fyre -> forgrunden gives aldrig tilbage -> proeven bliver roed. Beviser M4's invariant paa en maskine uden menneske. Co-Authored-By: Claude Opus 5.5 --- .github/mutant-giv-tilbage.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/mutant-giv-tilbage.sh b/.github/mutant-giv-tilbage.sh index a181830d..f89daf10 100644 --- a/.github/mutant-giv-tilbage.sh +++ b/.github/mutant-giv-tilbage.sh @@ -27,6 +27,9 @@ mut M1-giver-ikke-tilbage 'NSRunningApplication(processIdentifier: foer.forrestP mut M2-maaler-ikke 'guard foer.forrestPid > 0, efter.forrestPid != foer.forrestPid else { return ["took_screen": false] }' 'return ["took_screen": false]' # M3: launch giver ikke forgrunden tilbage (main.swift) MUT_FIL=main.swift mut M3-launch-giver-ikke-tilbage 'if (g["took_screen"] as? Bool) == true { for (k, v) in g { ls[k] = v } }' 'if false { for (k, v) in g { ls[k] = v } }' -# M4: kun et skift til maalprogrammet gives tilbage (reglen foer koersel 10) -mut M4-kun-maalprogrammet 'efter.forrestPid == maal || !menneskeRoerteNetop()' 'efter.forrestPid == maal' +# M4 (28/9): menneske-tjekket maa ikke spurioest blokere give-tilbage. Paa en +# maskine UDEN menneske skal forgrunden stadig gives tilbage - saa hvis nogen +# faar menneske-grenen til altid at fyre (og dermed altid lade forgrunden staa), +# SKAL proeven blive roed. +mut M4-menneske-tjek-blokerer 'if menneskeRoerteNetop() {' 'if true {' exit $fejl From c06305810731299382c49d9dba2f4ff289b96112 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 29 Sep 2026 00:29:33 +0800 Subject: [PATCH 007/214] doer (slice 1): computer_open - aabn en apps egen indgang bagfra, sikkert Baggrunds-doeren fra batch-2-spec'en, foerste skive. Modellen giver et intent (open_app/play_track/open_chat) + EN typet parameter, ALDRIG en URL - serveren bygger en fast app-URL og validerer parameteren strengt (kun cifre/bogstaver). Helperen aabner den med NSWorkspace .withoutActivation og dobbelt-tjekker mod en haardkodet scheme-allowlist (spotify/whatsapp/claude). Doeren navngiver sit maal via intent'et, saa porten ikke kalder den 'ukendt maal'. Baerer navigation, ALDRIG et send. test/doer.mjs (8 tjek) beviser: intet url-felt at injicere i, bad id/phone/bundle/intent afvist, praecis app-URL bygget. Swift-open-url + funktionen verificeres paa hhv. koereren og Gustavs Mac. Co-Authored-By: Claude Opus 4.8 --- helper/Sources/cmcp-helper/main.swift | 26 ++++++++++ mcp-server/index.js | 36 +++++++++++++ mcp-server/tools.js | 16 ++++++ test/doer.mjs | 75 +++++++++++++++++++++++++++ test/run-all.sh | 1 + 5 files changed, 154 insertions(+) create mode 100644 test/doer.mjs diff --git a/helper/Sources/cmcp-helper/main.swift b/helper/Sources/cmcp-helper/main.swift index 3ae96996..899ac87d 100644 --- a/helper/Sources/cmcp-helper/main.swift +++ b/helper/Sources/cmcp-helper/main.swift @@ -277,6 +277,32 @@ case "launch": } Out.ok(ls) +case "open-url": + // Doeren: aabn en app's EGEN indgang bagfra (spotify:track:, whatsapp://chat) + // uden at aktivere. Serveren bygger URL'en af en fast skabelon og validerer + // hver parameter; helperen dobbelt-tjekker mod en HAARDKODET scheme-allowlist, + // saa selv en fejl i serveren aldrig kan aabne file:/shortcuts:/osascript. + guard let raw = args.str("url"), let url = URL(string: raw), let scheme = url.scheme?.lowercased() else { + Out.fail("--url is missing or is not a valid URL", code: "bad-args") + } + let tilladteSchemes: Set = ["spotify", "whatsapp", "claude"] + guard tilladteSchemes.contains(scheme) else { + Out.fail("the scheme '\(scheme)' is not one of this door's app schemes (\(tilladteSchemes.sorted().joined(separator: ", ")))", code: "scheme-not-allowed") + } + let foerU = Skaerm.stand() + let cfg = NSWorkspace.OpenConfiguration() + cfg.activates = false + let sem = DispatchSemaphore(value: 0) + var aabenFejl: Error? = nil + NSWorkspace.shared.open(url, configuration: cfg) { _, error in aabenFejl = error; sem.signal() } + _ = sem.wait(timeout: .now() + 10) + if let e = aabenFejl { Out.fail("opening the \(scheme) door failed: \(e.localizedDescription)", code: "open-failed") } + var uu: [String: Any] = ["opened_scheme": scheme] + // Kan hente sig selv frem trods activates:false - giv forgrunden straks tilbage. + let gu = Skaerm.givTilbage(foer: foerU, tilPid: { NSWorkspace.shared.frontmostApplication?.processIdentifier }, ventMs: 4000) + for (k, v) in gu { uu[k] = v } + Out.ok(uu) + case "quit": guard let hvad = args.str("app") else { Out.fail("--app is missing", code: "bad-args") } // ⛔ Den her maaling er ikke pynt. Et program med ugemt arbejde svarer paa diff --git a/mcp-server/index.js b/mcp-server/index.js index 97ff0593..40d83790 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -502,6 +502,32 @@ async function runTool(name, args) { ...(args.background ? ['--background'] : [])]); return medSkaerm(textResult(r), r); } + case 'computer_open': { + // Doeren. Modellen giver et intent + EN parameter, aldrig en URL. Serveren + // bygger URL'en af en fast skabelon og validerer parameteren strengt, saa + // KUN cifre/bogstaver kan passere - ingen injektion, ingen fri scheme. + const intent = String(args.intent || ''); + if (intent === 'open_app') { + const bid = String(args.bundleId || ''); + if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$/.test(bid)) return errorResult('Refused: open_app needs a valid bundle id like com.spotify.client. Nothing was done.'); + const r = await callHelper(['launch', '--app', bid, '--background']); + return medSkaerm(textResult(r), r); + } + let url; + if (intent === 'play_track') { + const id = String(args.spotifyId || ''); + if (!/^[0-9A-Za-z]{22}$/.test(id)) return errorResult('Refused: play_track needs a 22-character Spotify track id (letters and digits only). Nothing was done.'); + url = `spotify:track:${id}`; + } else if (intent === 'open_chat') { + const phone = String(args.phone || '').replace(/[\s()-]/g, ''); + if (!/^\+?\d{4,15}$/.test(phone)) return errorResult('Refused: open_chat needs a phone number in international form, e.g. +4560174569. Nothing was done.'); + url = `whatsapp://send?phone=${encodeURIComponent(phone.replace(/^\+/, ''))}`; + } else { + return errorResult(`Refused: unknown intent '${intent}'. Use open_app, play_track or open_chat. Nothing was done.`); + } + const r = await callHelper(['open-url', '--url', url]); + return medSkaerm(textResult(r), r); + } case 'computer_quit': { // ⛔ 23/9: svaret baar intet `took_screen`, saa loggen kunne ikke goere // det op for den her vej. Et program der rejser et «vil du gemme?»-ark @@ -849,6 +875,16 @@ async function haandterKald(request) { targetBundleId = args.app ? await resolveBundleId(args.app) : await frontmostBundleId(); + // Doeren navngiver sit maal via intent'et (ikke via args.app), saa porten + // ser den rigtige app - ikke det der tilfaeldigvis er forrest - og ikke + // kalder den "ukendt maal" og spoerger. Et ugyldigt intent giver null, som + // saa afvises af porten, praecis som det skal. + if (name === 'computer_open') { + targetBundleId = args.intent === 'open_app' ? (args.bundleId ? String(args.bundleId) : null) + : args.intent === 'play_track' ? 'com.spotify.client' + : args.intent === 'open_chat' ? 'net.whatsapp.WhatsApp' + : null; + } // ⛔ FABLE 24/9: et LUKKET program findes ikke blandt de koerende, saa // `computer_launch` blev altid «ukendt maal» og afvist - vaerktoejet // kunne ikke det ene det er til. Hjaelperen svarer nu med det bundle-id diff --git a/mcp-server/tools.js b/mcp-server/tools.js index 7daabfbb..08374984 100644 --- a/mcp-server/tools.js +++ b/mcp-server/tools.js @@ -46,6 +46,22 @@ export const TOOLS = [ required: ['app'] } }, + { + name: 'computer_open', + tier: TIER.WRITE, + description: "Open an app's OWN door in the background - a Spotify track, a WhatsApp chat, or the app itself - without bringing it to the front. This is the way to reach an app whose window is covered, or that will not act from behind: it uses the app's own URL, not its window. It carries navigation ONLY, never a send - to send a message you still act on the open chat and the person confirms it. You give an `intent` and its one parameter, never a URL: the server builds a fixed, validated URL for that intent, and file:, shortcuts:, osascript and anything else are refused.", + inputSchema: { + type: 'object', + required: ['intent'], + properties: { + intent: { type: 'string', enum: ['open_app', 'play_track', 'open_chat'], + description: 'open_app: bring an app up in the background (needs bundleId). play_track: start a Spotify track (needs spotifyId, 22 chars). open_chat: open a WhatsApp chat to a phone number with NO pre-filled text (needs phone).' }, + bundleId: { type: 'string', description: 'For open_app: the app, e.g. com.spotify.client.' }, + spotifyId: { type: 'string', description: 'For play_track: the 22-character Spotify track id.' }, + phone: { type: 'string', description: 'For open_chat: the phone number in international form, e.g. +4560174569.' } + } + } + }, { name: 'computer_quit', tier: TIER.WRITE, diff --git a/test/doer.mjs b/test/doer.mjs new file mode 100644 index 00000000..2ae161b7 --- /dev/null +++ b/test/doer.mjs @@ -0,0 +1,75 @@ +// Døren (computer_open): åbn en app's egen indgang bagfra via et INTENT + én +// typet parameter - aldrig en fri URL fra modellen. Serveren bygger URL'en af +// en fast skabelon og validerer parameteren strengt. +// +// ⛔ HVORFOR DEN FINDES (28/9) +// En dør der tog en fri URL/scheme ville være en generisk bagdør: +// file:, shortcuts://run-shortcut → shell, osascript = Turing-komplet. Prøven +// beviser: (1) der er INGEN url-parameter at injicere i, (2) hver parameter +// valideres så kun cifre/bogstaver passerer, (3) serveren bygger præcis den +// forventede app-URL. Helperen er mocket, så intet åbnes. +import { spawn } from 'node:child_process'; +import { mkdtempSync, writeFileSync, chmodSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const fails = []; +const check = (l, c, d = '') => { console.log(`${c ? 'OK ' : 'DUMP'} ${l}${d ? ' - ' + d : ''}`); if (!c) fails.push(l); }; + +// Mock-helper: ekko den modtagne --url / --app som JSON, aabn intet. +const DIR = mkdtempSync(join(tmpdir(), 'cmcp-doer-')); +const fakeHelper = join(DIR, 'fake-helper'); +writeFileSync(fakeHelper, `#!/bin/bash +cmd="$1"; shift; url=""; app="" +while [ $# -gt 0 ]; do case "$1" in --url) url="$2"; shift 2;; --app) app="$2"; shift 2;; *) shift;; esac; done +if [ "$cmd" = "open-url" ]; then echo "{\\"ok\\":true,\\"echoed_url\\":\\"$url\\",\\"took_screen\\":false}" +elif [ "$cmd" = "launch" ]; then echo "{\\"ok\\":true,\\"result\\":\\"launched in the background\\",\\"echoed_app\\":\\"$app\\",\\"took_screen\\":false}" +else echo "{\\"ok\\":true}"; fi +`); +chmodSync(fakeHelper, 0o755); + +const srv = spawn('node', [join(ROOT, 'mcp-server/index.js')], + { env: { ...process.env, CMCP_HELPER: fakeHelper, CMCP_MODE: 'allow', CMCP_STATUS_IKON: '0', CMCP_STATE_DIR: join(DIR, 'state') }, stdio: ['pipe', 'pipe', 'pipe'] }); +let buf = '', n = 0; const w = new Map(); +srv.stdout.on('data', d => { buf += d; let i; while ((i = buf.indexOf('\n')) >= 0) { const l = buf.slice(0, i); buf = buf.slice(i + 1); try { const m = JSON.parse(l); w.get(m.id)?.(m); } catch {} } }); +const rpc = (m, p) => new Promise(r => { const id = ++n; w.set(id, r); srv.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method: m, params: p }) + '\n'); }); +const kald = async (args) => { const r = await rpc('tools/call', { name: 'computer_open', arguments: args }); const t = r.result?.content?.[0]?.text ?? ''; let d = null; try { d = JSON.parse(t); } catch {} return { fejl: !!r.result?.isError, tekst: t, data: d }; }; + +try { + await rpc('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'doer', version: '1' } }); + srv.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n'); + + const tools = (await rpc('tools/list')).result?.tools || []; + const doer = tools.find(t => t.name === 'computer_open'); + check('1 doeren findes', !!doer); + const props = Object.keys(doer?.inputSchema?.properties || {}); + check('1b der er INGEN url-parameter at injicere i', !props.includes('url'), props.join(',')); + check('1c parametrene er intent + typede felter', props.includes('intent') && props.includes('spotifyId') && props.includes('phone')); + + // Gyldige intents -> praecis den forventede app-URL (mock ekko). + const t1 = await kald({ intent: 'play_track', spotifyId: '4cOdK2wGLETKBW3PvgPWqT' }); + check('2 play_track bygger spotify:track: med id\'et', !t1.fejl && t1.data?.echoed_url === 'spotify:track:4cOdK2wGLETKBW3PvgPWqT', t1.data?.echoed_url || t1.tekst.slice(0, 80)); + const t2 = await kald({ intent: 'open_chat', phone: '+45 60 17 45 69' }); + check('3 open_chat bygger whatsapp://send?phone= (tegnene renset, ingen text)', !t2.fejl && t2.data?.echoed_url === 'whatsapp://send?phone=4560174569', t2.data?.echoed_url || t2.tekst.slice(0, 80)); + + // Ugyldige parametre -> AFVIST, intet aabnet. + const b1 = await kald({ intent: 'play_track', spotifyId: 'file:///etc/passwd' }); + check('4 et forsoeg paa at smugle en sti ind i spotifyId afvises', b1.fejl && !b1.data?.echoed_url, b1.tekst.slice(0, 70)); + const b2 = await kald({ intent: 'open_chat', phone: '4560; do shell script' }); + check('5 et telefonnummer med skal-tekst afvises', b2.fejl, b2.tekst.slice(0, 70)); + const b3 = await kald({ intent: 'run_shortcut', spotifyId: 'x' }); + check('6 et ukendt intent afvises (ingen fri handling)', b3.fejl, b3.tekst.slice(0, 70)); + const b4 = await kald({ intent: 'open_app', bundleId: 'com.x; rm -rf /' }); + check('7 en bundle id med skal-tegn afvises', b4.fejl, b4.tekst.slice(0, 70)); + + // En medsendt fri url AFVISES helt: den er ikke et felt paa vaerktoejet, saa + // der er ingen vej at injicere en scheme ad. (Staerkere end at ignorere den.) + const t3 = await kald({ intent: 'play_track', spotifyId: '4cOdK2wGLETKBW3PvgPWqT', url: 'file:///etc/passwd' }); + check('8 en medsendt fri url AFVISES (intet felt at injicere i)', t3.fejl && !t3.data?.echoed_url, t3.tekst.slice(0, 70)); +} finally { + srv.kill(); +} +console.log(fails.length ? `DUMPET: ${fails.length} tjek` : 'Alle tjek bestået.'); +process.exit(fails.length ? 1 : 0); diff --git a/test/run-all.sh b/test/run-all.sh index 2b36940a..05cf007c 100755 --- a/test/run-all.sh +++ b/test/run-all.sh @@ -93,6 +93,7 @@ run "paastande" "node test/claims.mjs" # gennem en attrap, saa den koerer HVER gang og viser ingenting. run "fejl-lukket" "node test/failclosed.mjs" run "laas fejler lukket" "node test/laas-fejler-lukket.mjs" +run "doeren" "node test/doer.mjs" run "fejlbeskeder" "node test/errors.mjs" run "flere agenter" "node test/concurrent.mjs" From 35fbb6af4382f313b96a0de7428b811e13db48a0 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 29 Sep 2026 01:38:26 +0800 Subject: [PATCH 008/214] doer (slice 1) doc-sync: computer_open paa alle flader, tal 30->31 Tilfoej computer_open til vaerktoejslisterne (README, llms.txt, tools.html, capability-matrix), bump tool-tallet 30->31 (14 laesende, 17 skrivende) paa alle tekstflader + gemini-extension.json. Udvid ORD-ordlisten i baade sync-tal.py og claims.mjs forbi 'thirty' (crashede/parsede undefined ved 31). Ret to test-hygiejne- fund: doer.mjs saetter fake osascript (vagt 26), og open-url's fejltekst hejser join'et ud i en variabel saa lintens streng-udtraekker ikke eksponerer et dansk variabelnavn (vagt 27). claims.mjs groen. Ingen kode-logik roert. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 +++--- docs/404.html | 2 +- docs/blog/driving-macos-from-an-ai-agent.html | 2 +- docs/docs/capability-matrix/index.html | 8 ++++---- docs/docs/install-claude-code/index.html | 6 +++--- docs/docs/install-claude-desktop/index.html | 6 +++--- docs/docs/install-codex/index.html | 6 +++--- docs/docs/install-copilot/index.html | 6 +++--- docs/docs/install-cursor/index.html | 6 +++--- docs/docs/install-vscode/index.html | 6 +++--- docs/index.html | 10 +++++----- docs/learn/computer-use-mcp/index.html | 2 +- docs/learn/owasp-mcp-top-10/index.html | 2 +- docs/llms-install.md | 4 ++-- docs/llms.txt | 6 +++--- docs/tools.html | 10 +++++++--- docs/use-cases/unattended-runs/index.html | 2 +- .../without-taking-over-your-screen/index.html | 12 ++++++------ gemini-extension.json | 2 +- helper/Sources/cmcp-helper/main.swift | 3 ++- mcp-server/README.md | 6 +++--- scripts/sync-tal.py | 4 +++- test/claims.mjs | 4 +++- test/doer.mjs | 4 +++- 24 files changed, 68 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ebe09e0e..c87de2e5 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ it refuses. ## Tools -**30 tools: fourteen that look, sixteen that touch.** Twenty are offered by +**31 tools: fourteen that look, seventeen that touch.** Twenty are offered by default, and the agent uses them without asking - the same way a browser tool drives a browser. Two gates survive that, and they are the two that matter: @@ -208,12 +208,12 @@ Both surviving gates are mutation-proved: break them in the source and the refusal turns into a free pass, which is how we know the test can fail. -> **What you get today, honestly.** `npx @agent360/computer-mcp` currently serves **0.1.0**, which has 12 tools. The 30 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now. +> **What you get today, honestly.** `npx @agent360/computer-mcp` currently serves **0.1.0**, which has 12 tools. The 31 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now. **Look:** `computer_pending` · `computer_screenshot` · `computer_record` · `computer_inspect` · `computer_find` · `computer_wait_for` · `computer_focused` · `computer_apps` · `computer_windows` · `computer_permissions` · `computer_displays` · `computer_menus` · `computer_audit` · `computer_learning` -**Touch:** `computer_launch` · `computer_quit` · `computer_paste` · `computer_window` · `computer_space` · `computer_menu` · `computer_press` · `computer_set_value` · `computer_ask_user` · +**Touch:** `computer_launch` · `computer_open` · `computer_quit` · `computer_paste` · `computer_window` · `computer_space` · `computer_menu` · `computer_press` · `computer_set_value` · `computer_ask_user` · `computer_click` · `computer_drag` · `computer_type` · `computer_key` · `computer_scroll` · `computer_move` · `computer_activate` diff --git a/docs/404.html b/docs/404.html index 9f015b8b..b66f7e90 100644 --- a/docs/404.html +++ b/docs/404.html @@ -87,7 +87,7 @@

The pages that do exist

Install for Claude Code Install for Codex Install for Cursor -All 30 tools +All 31 tools Capability matrix Threat model MCP security diff --git a/docs/blog/driving-macos-from-an-ai-agent.html b/docs/blog/driving-macos-from-an-ai-agent.html index e4f5c76c..67bdb168 100644 --- a/docs/blog/driving-macos-from-an-ai-agent.html +++ b/docs/blog/driving-macos-from-an-ai-agent.html @@ -156,7 +156,7 @@

What we would tell someone starting today

-

Computer MCP is the macOS computer-use MCP server these findings came out of. Thirty tools, no API key, MIT, and a security model that is specific about what it does not cover. The recipes folder collects app-specific notes like the Electron numbers above, and it takes contributions.

+

Computer MCP is the macOS computer-use MCP server these findings came out of. Thirty-one tools, no API key, MIT, and a security model that is specific about what it does not cover. The recipes folder collects app-specific notes like the Electron numbers above, and it takes contributions.

diff --git a/docs/docs/capability-matrix/index.html b/docs/docs/capability-matrix/index.html index f63f79f2..0ecc652d 100644 --- a/docs/docs/capability-matrix/index.html +++ b/docs/docs/capability-matrix/index.html @@ -8,7 +8,7 @@ - + @@ -119,7 +119,7 @@

Walls we clear partly, and say so

The Dock and the status iconsmeasuredWi-Fi, the clock, the battery, and every app in the Dock were invisible until today, and the reason was not that they are special: they simply live outside any window. Measured: the Dock has zero windows and 32 dock items hanging off the application itself; Control Center has zero windows and nine menu bar items. The search walked down through windows only, so everything outside one was unreachable. It now falls back to the application element when an app has no windows - computer_find returns them like anything else, with a center you can click and a pressable flag. Nothing changes for apps that do have windows. Whether an update breaks your permissionsmeasuredIt does not. The grant belongs to the host - the app that launches the server, which is your terminal, your editor or Claude Desktop - and not to our binary. Measured directly: the same helper run from a path macOS had never seen still reports Accessibility, and so does a copy with a byte appended, which gives it a different signature entirely. So upgrading the package does not send you back to System Settings. The flip side is worth knowing too: the permission you granted covers anything that host launches, not this tool alone. Knowing that something is waiting for youmeasuredIf the server may never take the screen, it cannot knock either. A write that would need a dialog is refused, and the explanation goes back to the model - which only reaches you if you read that particular conversation. computer_pending makes it visible instead: what was asked for, when, and why it stopped. It is a list, not a button - nothing there can be approved from there, because a consent granted without a human is the one thing the gate exists to prevent. To allow the work you change the mode, and the queue simply says what is waiting. -Never taking over the screen at allmeasuredThis is the default. The server runs in background mode unless you set CMCP_BACKGROUND=0, and a typo does not turn it off - only 0, false, no or off count as a no.

What that means for the tools. Until 21 September background mode hid all thirteen of those that could take the screen, which made it the product minus half its hands. That was because "takes the screen" was a property of the tool's name. It is not - it is a property of the delivery. An event sent to the global input stream lands in whatever window you are using; the same event delivered into one app's own queue does not. So four of them - computer_type, computer_key, computer_scroll and computer_click - now take an app, and with it they are offered in background mode. Without one the call is refused with an explanation of what to pass instead. Measured: 22 of the 30 are offered in background, eight of them writing - up from 15 and three.

Measured on a machine in use, through the shipped product: text typed into an app that was not in front arrived in that app, the pointer stayed where the person had left it, and the front window did not change. Typing goes into the field that app has focus in and is read back; a field that does not accept that (and every password field) gets keystrokes instead, which an app in the background can drop, and the answer then says the text was not confirmed. That measurement covers typing and key presses. Clicks and scrolls take the same route and have not been measured - a mouse event carries no window number, and whether an app accepts one it never saw the pointer reach is a question we have not answered. The answer still reports on the screen honestly; it does not claim the click landed. Every key press, click, scroll and typed string now carries took_screen in its own answer, derived from what the server did rather than from what happened around it - so it cannot mistake the person moving their own mouse for the server moving it.

Still hidden in background mode, and some always will be: computer_move is the pointer, computer_activate and computer_space exist to move you, and computer_ask_user is a dialog. computer_launch joins the quiet side with background: true - the app starts behind what you are doing. quit, window, drag and paste have not been rebuilt yet.

One thing it does to other apps, and you should know it. To find a password field in Slack, Notion or Discord at all, Chromium's accessibility tree has to be on - so taking a screenshot switches it on in every app on screen, not only the one you asked about. Without that, the blackout is blind in exactly the apps where people type passwords. The cost is carried by those apps: they build and keep a tree they would not otherwise have. CMCP_INGEN_ELECTRON=1 turns it off, and the blackout is then blind there.

The honest limit that does not change: this is a promise about what we do. Press a button and the app may open a window of its own; that is its choice, not ours. +Never taking over the screen at allmeasuredThis is the default. The server runs in background mode unless you set CMCP_BACKGROUND=0, and a typo does not turn it off - only 0, false, no or off count as a no.

What that means for the tools. Until 21 September background mode hid all thirteen of those that could take the screen, which made it the product minus half its hands. That was because "takes the screen" was a property of the tool's name. It is not - it is a property of the delivery. An event sent to the global input stream lands in whatever window you are using; the same event delivered into one app's own queue does not. So four of them - computer_type, computer_key, computer_scroll and computer_click - now take an app, and with it they are offered in background mode. Without one the call is refused with an explanation of what to pass instead. Measured: 23 of the 31 are offered in background, nine of them writing - up from 15 and three.

Measured on a machine in use, through the shipped product: text typed into an app that was not in front arrived in that app, the pointer stayed where the person had left it, and the front window did not change. Typing goes into the field that app has focus in and is read back; a field that does not accept that (and every password field) gets keystrokes instead, which an app in the background can drop, and the answer then says the text was not confirmed. That measurement covers typing and key presses. Clicks and scrolls take the same route and have not been measured - a mouse event carries no window number, and whether an app accepts one it never saw the pointer reach is a question we have not answered. The answer still reports on the screen honestly; it does not claim the click landed. Every key press, click, scroll and typed string now carries took_screen in its own answer, derived from what the server did rather than from what happened around it - so it cannot mistake the person moving their own mouse for the server moving it.

Still hidden in background mode, and some always will be: computer_move is the pointer, computer_activate and computer_space exist to move you, and computer_ask_user is a dialog. computer_launch joins the quiet side with background: true - the app starts behind what you are doing. quit, window, drag and paste have not been rebuilt yet.

One thing it does to other apps, and you should know it. To find a password field in Slack, Notion or Discord at all, Chromium's accessibility tree has to be on - so taking a screenshot switches it on in every app on screen, not only the one you asked about. Without that, the blackout is blind in exactly the apps where people type passwords. The cost is carried by those apps: they build and keep a tree they would not otherwise have. CMCP_INGEN_ELECTRON=1 turns it off, and the blackout is then blind there.

The honest limit that does not change: this is a promise about what we do. Press a button and the app may open a window of its own; that is its choice, not ours. Password managers, in the tree as well as the imagemeasuredScreenshots black out Keychain Access, Apple Passwords, 1Password, Bitwarden, LastPass and Dashlane entirely. Until today the accessibility tree did not: computer_inspect and computer_find would hand over the contents of the very window the image hides - as a read, so with no consent, in read-only mode, and in background mode. Anything not marked as a secure field came with it: a revealed password sitting in a static field, a one-time code, a secure note, every username. Both now stop at the app: find returns nothing, inspect returns one node saying denied and why - silence with a reason beats silence. Measured on a running app added to the list: 3 matches and 33 nodes became 0 and 1, with zero values. Acting quietly inside an appmeasuredThe quiet route - computer_press and computer_set_value, which work on a window behind another one without moving your pointer - only exists where the app publishes an accessibility tree. Measured on five running apps: Chrome exposes 321 elements across 4 windows, 148 of them things you can actually press or type into. An Electron app measured before its tree was switched on exposed its 2 windows and 16 elements inside them, none usable; with the tree on, the same two windows gave 728 buttons. Finder's desktop window is 103 icons and nothing pressable. Where the tree is empty the only route left is a screenshot and a click by coordinate - which does move the real cursor. So "works on a window behind another one" is true of the app, not of every app. Reaching a full-screen appmeasuredcomputer_space switches desktop the way Control+Arrow does, because macOS gives every full-screen window its own desktop - a window the agent cannot find is often simply on another one. It is the one tool here that deliberately does move what you are looking at, so it asks every time, in every mode, like quitting an app. Two honest limits, both measured: if the system shortcut for switching desktops is off on this machine it says so and sends nothing rather than pressing a dead key; and it reports whether the desktop actually changed by comparing which windows are on screen before and after, so it never claims a switch it cannot prove. Two earlier ways of checking that were wrong - counting accessibility windows drifted 10 to 8 with nothing changing, and the system notification never reaches a short-lived command-line process. @@ -147,10 +147,10 @@

Not yet, and honestly named

Windows or Linuxwill notmacOS only, and none planned. The accessibility model is the product. -

The thirty tools

+

The thirty-one tools

- +
Look (14)computer_permissions · computer_record · computer_apps · computer_windows · computer_displays · computer_menus · computer_screenshot · computer_inspect · computer_pending · computer_audit · computer_find · computer_wait_for · computer_focused · computer_learning
Touch (16)computer_launch · computer_quit · computer_drag · computer_space · computer_paste · computer_window · computer_menu · computer_set_value · computer_press · computer_ask_user · computer_click · computer_move · computer_scroll · computer_type · computer_key · computer_activate
Touch (17)computer_launch · computer_open · computer_quit · computer_drag · computer_space · computer_paste · computer_window · computer_menu · computer_set_value · computer_press · computer_ask_user · computer_click · computer_move · computer_scroll · computer_type · computer_key · computer_activate

In CMCP_MODE=readonly only the fourteen reading tools are listed at all. A model that can see a tool will eventually try it, and a refusal per call is noise.

diff --git a/docs/docs/install-claude-code/index.html b/docs/docs/install-claude-code/index.html index cc0407b9..a4b4d7b4 100644 --- a/docs/docs/install-claude-code/index.html +++ b/docs/docs/install-claude-code/index.html @@ -91,7 +91,7 @@

Install Computer MCP for Claude Code

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for Claude Code

Start in read-only. Set CMCP_MODE=readonly for the first run and -the sixteen write tools are not even listed to the model. It can look at your screen and it +the seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything, and you can move to ask when you have. Step 1b below shows where the setting goes.

@@ -202,7 +202,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach Threat model MCP security checklist diff --git a/docs/docs/install-claude-desktop/index.html b/docs/docs/install-claude-desktop/index.html index e83eeb95..be672a5b 100644 --- a/docs/docs/install-claude-desktop/index.html +++ b/docs/docs/install-claude-desktop/index.html @@ -91,7 +91,7 @@

Install Computer MCP for Claude Desktop

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for Claude Desktop

Start in read-only. Set CMCP_MODE=readonly for the first run and the -sixteen write tools are not even listed to the model. It can look at your screen and it cannot +seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything.

@@ -229,7 +229,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach When not to use it Uninstall diff --git a/docs/docs/install-codex/index.html b/docs/docs/install-codex/index.html index 11974e6d..a22e8f49 100644 --- a/docs/docs/install-codex/index.html +++ b/docs/docs/install-codex/index.html @@ -91,7 +91,7 @@

Install Computer MCP for OpenAI Codex

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for OpenAI Codex

Start in read-only. Set CMCP_MODE=readonly for the first run and -the sixteen write tools are not even listed to the model. It can look at your screen and it +the seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything, and you can move to ask when you have. Step 1b below shows where the setting goes.

@@ -202,7 +202,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach Threat model MCP security checklist diff --git a/docs/docs/install-copilot/index.html b/docs/docs/install-copilot/index.html index 84de4032..3deb78e4 100644 --- a/docs/docs/install-copilot/index.html +++ b/docs/docs/install-copilot/index.html @@ -91,7 +91,7 @@

Install Computer MCP for GitHub Copilot

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for GitHub Copilot

Start in read-only. Set CMCP_MODE=readonly for the first run and the -sixteen write tools are not even listed to the model. It can look at your screen and it cannot +seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything.

@@ -228,7 +228,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach When not to use it Uninstall diff --git a/docs/docs/install-cursor/index.html b/docs/docs/install-cursor/index.html index 96986f86..7f6a7dda 100644 --- a/docs/docs/install-cursor/index.html +++ b/docs/docs/install-cursor/index.html @@ -91,7 +91,7 @@

Install Computer MCP for Cursor

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for Cursor

Start in read-only. Set CMCP_MODE=readonly for the first run and -the sixteen write tools are not even listed to the model. It can look at your screen and it +the seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything, and you can move to ask when you have. Step 1b below shows where the setting goes.

@@ -215,7 +215,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach Threat model MCP security checklist diff --git a/docs/docs/install-vscode/index.html b/docs/docs/install-vscode/index.html index 2a6ad8b7..ffd8991f 100644 --- a/docs/docs/install-vscode/index.html +++ b/docs/docs/install-vscode/index.html @@ -91,7 +91,7 @@

Install Computer MCP for VS Code

What you get today, honestly. npx currently serves -0.1.0, which has 12 tools. The code in the repository has 30: menu bar +0.1.0, which has 12 tools. The code in the repository has 31: menu bar access, window control, moving windows between screens, pasting, opening and quitting apps, waiting for something to appear, writing into a field behind another window, and asking you for a password without the model ever seeing it. @@ -108,7 +108,7 @@

Install Computer MCP for VS Code

Start in read-only. Set CMCP_MODE=readonly for the first run and the -sixteen write tools are not even listed to the model. It can look at your screen and it cannot +seventeen write tools are not even listed to the model. It can look at your screen and it cannot touch anything. You see the whole tool list before you grant anything.

@@ -218,7 +218,7 @@

FAQ

Is there a Windows or Linux build?

No, and there is no plan for one. The three guarantees are built on macOS APIs: the Accessibility API for what an element is, and ScreenCaptureKit for the screenshot. A port would be a different product with different guarantees, not the same one recompiled.

Related

-
All 30 tools +
All 31 tools What it can and cannot reach When not to use it Uninstall diff --git a/docs/index.html b/docs/index.html index d4b3c8fe..a16ac7b3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ Computer MCP - computer use you can actually leave running - + @@ -183,8 +183,8 @@

What still asks, and

Install

-

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 30 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

-

Add it to your MCP client, grant two macOS permissions, done. It starts ready to work: twenty-two of the thirty, no asking, except your password manager, anything that deletes or clears, and starting a screen recording. It runs in the background by default, so nothing takes your screen; CMCP_BACKGROUND=0 adds the eight that do.

+

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 31 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

+

Add it to your MCP client, grant two macOS permissions, done. It starts ready to work: twenty-three of the thirty-one, no asking, except your password manager, anything that deletes or clears, and starting a screen recording. It runs in the background by default, so nothing takes your screen; CMCP_BACKGROUND=0 adds the eight that do.

@@ -270,8 +270,8 @@

No API key, no se
-

thirty tools

-

fourteen that look, sixteen that touch. Grouped by what you would +

thirty-one tools

+

fourteen that look, seventeen that touch. Grouped by what you would actually want, with the names underneath in case you are wiring this up yourself.

Claude Codeclaude mcp add computer -- npx -y @agent360/computer-mcp
Cursor / VS Code / CodexIn mcp.json: {"command":"npx","args":["-y","@agent360/computer-mcp"]}
diff --git a/docs/learn/computer-use-mcp/index.html b/docs/learn/computer-use-mcp/index.html index f3520aae..b9f8317e 100644 --- a/docs/learn/computer-use-mcp/index.html +++ b/docs/learn/computer-use-mcp/index.html @@ -262,7 +262,7 @@

Related

MCP security: what a server can reachLeaving an agent running overnightWhen not to use it -All 30 tools +All 31 tools

Computer MCP is built by Agent360, who also make diff --git a/docs/learn/owasp-mcp-top-10/index.html b/docs/learn/owasp-mcp-top-10/index.html index 0b10be36..09aadd7a 100644 --- a/docs/learn/owasp-mcp-top-10/index.html +++ b/docs/learn/owasp-mcp-top-10/index.html @@ -127,7 +127,7 @@

The ten, and what we do about each

+talked into trying. Measured: 31 tools in full mode, 11 in read-only.
So it can...What that means
addressed Tools carry a tier, and CMCP_MODE=readonly does not merely refuse the write tools - it does not list them. A tool the model cannot see is a tool it cannot be -talked into trying. Measured: 30 tools in full mode, 11 in read-only.
MCP03Tool Poisoning not our layer This is about a malicious server diff --git a/docs/llms-install.md b/docs/llms-install.md index d66fc486..4560da19 100644 --- a/docs/llms-install.md +++ b/docs/llms-install.md @@ -9,11 +9,11 @@ finished until `computer_permissions` confirms it. Computer MCP drives macOS itself: it reads the accessibility tree, takes screenshots, clicks, types, presses buttons, chooses menu items and moves -windows. The source has **30 tools, 14 of them read-only**, no API key, and +windows. The source has **31 tools, 14 of them read-only**, no API key, and no network calls of its own. -> **What you get today, honestly.** `npx @agent360/computer-mcp` currently serves **0.1.0**, which has 12 tools. The 30 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now. +> **What you get today, honestly.** `npx @agent360/computer-mcp` currently serves **0.1.0**, which has 12 tools. The 31 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now. The design premise is that it is safe to leave running: password fields are blacked out in memory before a screenshot is written to disk, writes go through a consent gate, and every action lands in a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where that never stores typed text in clear. diff --git a/docs/llms.txt b/docs/llms.txt index bfbdaf4a..30931017 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -6,7 +6,7 @@ > log that only grows. No account, no API key, no model inside it. MIT. # FORBEHOLD -VERSION: npx serves 0.1.0, which has 12 tools. The 30 tools described below +VERSION: npx serves 0.1.0, which has 12 tools. The 31 tools described below the source: they are built and tested but not published yet. Do not tell a user that a tool is available after an npx install unless it is one of the twelve. # /FORBEHOLDInstall: `npx @agent360/computer-mcp` (ships a signed universal binary; no Swift needed) @@ -52,7 +52,7 @@ for, because that machine also holds passwords, keys and production terminals. password managers still ask every time, terminals and editors once per session, and that is not configurable -## The 30 tools, 14 of them read-only +## The 31 tools, 14 of them read-only Look: computer_screenshot, computer_record, computer_inspect, computer_find, computer_wait_for, computer_focused, computer_apps, computer_windows, computer_displays, @@ -61,7 +61,7 @@ computer_permissions, computer_pending computer_audit computer_learning -Touch: computer_launch, computer_quit, computer_paste, computer_window, computer_space, computer_menu, computer_press, computer_set_value, computer_ask_user, computer_click, +Touch: computer_launch, computer_open, computer_quit, computer_paste, computer_window, computer_space, computer_menu, computer_press, computer_set_value, computer_ask_user, computer_click, computer_type, computer_key, computer_scroll, computer_move, computer_drag, computer_activate computer_ask_user returns only true or false - never text. It is how a human diff --git a/docs/tools.html b/docs/tools.html index c4c27786..db4556b7 100644 --- a/docs/tools.html +++ b/docs/tools.html @@ -4,7 +4,7 @@ Tool reference - Computer MCP - + @@ -71,10 +71,10 @@

Tool reference

-

thirty tools. fourteen look, sixteen touch. The useful part of this page is not the parameter lists - it is knowing which tool to reach for when the obvious one is the wrong one.

+

thirty-one tools. fourteen look, seventeen touch. The useful part of this page is not the parameter lists - it is knowing which tool to reach for when the obvious one is the wrong one.

-

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 30 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

+

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 31 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

The one habit that matters. Find things by name, not by pixel. computer_inspect and computer_find return the frame of every element, so the agent can act on "the Log in button" rather than on a coordinate that stops being true the moment a window moves. Reach for computer_click only when there is no accessibility element to aim at: a canvas, a map, an image.

@@ -112,6 +112,10 @@

computer_launchwrite

Open an application, by bundle ID or by its plain name. computer_activate can only bring forward something already running; this starts what is closed. Already running, it just comes forward, and the answer says which of the two happened.

Launching cannot lose anything, so it is not on the always-ask list the way quitting is - but it is still a write, and it goes through the same gate as every other write: refused in read-only mode, and in ask mode the first one asks for the session. Quitting is separate because it can lose work, and it asks every single time.

+

computer_openwrite

+

Open an app's own door in the background - a Spotify track, a WhatsApp chat, or the app itself - via the app's own URL, never a send. You give an intent and its one parameter, not a URL: the server builds a fixed, validated URL for that intent, and anything else is refused.

+

It carries navigation only. To send a message you still act on the chat once it is open, and the person confirms it - opening a chat is not sending into it.

+

computer_quitwrite

Ask an application to quit, the same way Cmd+Q does. It never kills a process: the app still gets to ask you about unsaved work, and an agent should trigger that question rather than route around it.

Quitting can lose work, so this asks every time, in every mode - like closing a window, and like a menu item that looks destructive.

diff --git a/docs/use-cases/unattended-runs/index.html b/docs/use-cases/unattended-runs/index.html index 1b01e661..871cfe2d 100644 --- a/docs/use-cases/unattended-runs/index.html +++ b/docs/use-cases/unattended-runs/index.html @@ -166,7 +166,7 @@

FAQ

What should I do before starting a long run?

Look at your own screen. Close terminals with credentials in the scrollback and quit the password manager. The redaction covers what macOS marks as secure; it cannot cover a secret sitting in a plain text window.

Related

diff --git a/docs/use-cases/without-taking-over-your-screen/index.html b/docs/use-cases/without-taking-over-your-screen/index.html index 69ee5a57..3cfc6ae1 100644 --- a/docs/use-cases/without-taking-over-your-screen/index.html +++ b/docs/use-cases/without-taking-over-your-screen/index.html @@ -8,15 +8,15 @@ - + - + - + +

Kontaktark · hver use case, før og efter (B3)

+

Trukket automatisk fra filmene i dette CI-job. Intet er optaget på Gustavs Mac.

+{''.join(rows)}
ScenarieBaggrundForgrund
+

Parallelle agenter

+
{par_rows}
+""" + (ud_dir / "index.html").write_text(html, encoding="utf-8") + print(f"Kontaktark skrevet: {ud_dir / 'index.html'} ({len(use_cases)} scenarier, {len(parallelle)} parallel-film)") + + +if __name__ == "__main__": + main() From f22496a52e66c884da4910b5bdbc200a4a1e1243 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Fri, 2 Oct 2026 14:30:18 +0800 Subject: [PATCH 059/214] =?UTF-8?q?sikkerhed:=20et=20blindt=20koordinat-kl?= =?UTF-8?q?ik=20fejlede=20=C3=85BENT=20i=20stedet=20for=20lukket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fundet af Opus i et konsulentpanel om canvas-blinde apps (2/10): knapErFarlig() behandlede "intet fundet på punktet", "fundet uden navn/beskrivelse" og "opslaget fejlede" som IKKE farligt for computer_click - selvom et koordinat-klik rammer skærmen uanset om opslaget her lykkes. Et "Delete" i en canvas-tegnet dialog, et fjernskrivebord, eller ethvert punkt serveren ikke kan identificere, kunne rammes uden at nogen blev spurgt - stik imod README's løfte om at alt der sletter eller rydder spørger hver gang. Den oprindelige begrundelse ("et tryk der ikke kan slås op, finder heller ikke noget at trykke på") holder for computer_press, som ikke kan udføres uden et fundet element - men ikke for et klik. De tre tilfælde fejler nu LUKKET (spørg) for klik, mens press er uændret (fejler stadig åbent, med rette). test/knap-ord.mjs udvidet med tre nye tjek (intet fundet, intet navn, opslaget fejler) + ny kalibrering. Mutant Q26 i .github/mutanter-porte.json. Begge bekræftet manuelt: reverteres fixet, går præcis de tre nye tjek røde. --- .github/mutanter-porte.json | 12 ++++++++++++ mcp-server/index.js | 27 ++++++++++++++++++++------- test/knap-ord.mjs | 19 ++++++++++++++++++- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/.github/mutanter-porte.json b/.github/mutanter-porte.json index 6ee5aa9d..9f61c512 100644 --- a/.github/mutanter-porte.json +++ b/.github/mutanter-porte.json @@ -850,5 +850,17 @@ " alwaysAsk: menuFarlig" ] ] + }, + { + "navn": "Q26-klik-fejler-aabent", + "fil": "mcp-server/index.js", + "proeve": "test/knap-ord.mjs", + "forventet": "6 intet fundet", + "par": [ + [ + " if (!d?.found) return true;", + " if (!d?.found) return false;" + ] + ] } ] diff --git a/mcp-server/index.js b/mcp-server/index.js index 0241ee59..46a44b18 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -470,19 +470,32 @@ async function menuGenvejErFarlig(app, path) { /// «Erase», trykket med computer_press eller ramt af et klik, spurgte aldrig. /// Samme ord som menuen. Kendes elementets navn ikke, kan det ikke doemmes herfra: /// et tryk der ikke kan slaas op, finder heller ikke noget at trykke paa. +/// ⛔ 2/10 (Opus-panelet, canvas-spørgsmålet): den sætning gælder KUN press - et +/// tryk der ikke kan slås op, rammer intet, så at fejle åbent er harmløst der. +/// Et KOORDINAT-KLIK rammer skærmen uanset om opslaget her lykkes. Før stod +/// "intet fundet"/"intet navn"/en fejl alle som `false` (ikke farligt) - netop +/// de tre tilstande hvor serveren IKKE ved hvad der klikkes på (en canvas-tegnet +/// dialog, et fjernskrivebord, et navnløst element). README's løfte holdt ikke +/// på den vej: et "Delete" kunne rammes blindt uden at nogen blev spurgt. Et +/// punkt vi ikke kan identificere, er nu farligt - spørg, fail CLOSED. async function knapErFarlig(name, args) { - try { - if (name === 'computer_press') { + if (name === 'computer_press') { + try { const { a, soeg } = trykArgv(args); const d = await callHelper([...a, '--dry'], { stdin: JSON.stringify(soeg), timeout: 15000 }); const el = d?.would_press; return !!el && [el.name, ...(el.names || []), el.title].filter(Boolean).some(n => menuSerFarlig(n)); - } - if (name === 'computer_click' && Number.isFinite(args.x) && Number.isFinite(args.y)) { + } catch { return false; } + } + if (name === 'computer_click' && Number.isFinite(args.x) && Number.isFinite(args.y)) { + try { const d = await callHelper(['at', '--x', String(args.x), '--y', String(args.y)], { timeout: 8000 }); - return !!d?.found && [d.title, d.description].filter(Boolean).some(n => menuSerFarlig(n)); - } - } catch {} + if (!d?.found) return true; + const navne = [d.title, d.description].filter(Boolean); + if (!navne.length) return true; + return navne.some(n => menuSerFarlig(n)); + } catch { return true; } + } return false; } diff --git a/test/knap-ord.mjs b/test/knap-ord.mjs index bcb858b3..72535329 100644 --- a/test/knap-ord.mjs +++ b/test/knap-ord.mjs @@ -64,7 +64,24 @@ ved('Delete'); f = klikket(); await kald('computer_click', { app: 'com.apple.TextEdit', x: 100, y: 200 }); check('4 et klik der rammer «Delete», sker IKKE uden et ja', klikket() === f); -check('5 kalibrering: porten spurgte et menneske om 1, 2 og 4', spoerger.gangeSpurgt() === 3, `spurgt ${spoerger.gangeSpurgt()} gange`); +// ⛔ 2/10 (Opus-panelet): et klik rammer UANSET om opslaget her kan sige hvad +// der er på punktet - i modsætning til press, som slet ikke kan udføres uden +// et fundet element. Før fejlede disse tre tilfælde ÅBENT (ikke farligt). +writeFileSync(AT, JSON.stringify({ ok: true, found: false })); f = klikket(); +await kald('computer_click', { app: 'com.apple.TextEdit', x: 10, y: 10 }); +check('6 intet fundet på punktet: klikket sker IKKE uden et ja (fail closed)', klikket() === f); + +writeFileSync(AT, JSON.stringify({ ok: true, found: true, bundleId: 'com.apple.TextEdit', role: 'AXUnknown' })); f = klikket(); +await kald('computer_click', { app: 'com.apple.TextEdit', x: 10, y: 10 }); +check('7 fundet, men uden navn eller beskrivelse: klikket sker IKKE uden et ja', klikket() === f); + +const stubKilde = readFileSync(STUB, 'utf8'); +writeFileSync(STUB, stubKilde.replace(/^ at\) cat .*$/m, ' at) exit 1 ;;')); f = klikket(); +await kald('computer_click', { app: 'com.apple.TextEdit', x: 10, y: 10 }); +check('8 opslaget selv fejler: klikket sker IKKE uden et ja', klikket() === f); +writeFileSync(STUB, stubKilde); + +check('9 kalibrering: porten spurgte et menneske om 1, 2, 4, 6, 7 og 8', spoerger.gangeSpurgt() === 6, `spurgt ${spoerger.gangeSpurgt()} gange`); srv.kill(); console.log(fails.length ? `DUMPET: ${fails.length} tjek` : 'Alle tjek bestået.'); process.exit(fails.length ? 1 : 0); From a3483c54e97deedd1fffcb8b62ab8d3f8d6df471 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Fri, 2 Oct 2026 14:39:42 +0800 Subject: [PATCH 060/214] =?UTF-8?q?D3:=20f=C3=B8lg-panelets=20"Show=20me?= =?UTF-8?q?=20where"=20-=20peger=20p=C3=A5=20agentens=20m=C3=A5l=20uden=20?= =?UTF-8?q?selv=20at=20tage=20fokus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Følg-panelet (klik på en session i menuen) viste kun løbende tekst om hvad agenten gør. D3 bad om mere: "panelet peger på det program/vindue agenten arbejder i, uden at tage forgrunden af sig selv - menneskets klik er det eneste der flytter fokus". status.js: statusHandling() tager nu en valgfri tredje parameter - den raa app-streng (bundle-id ELLER synligt navn) serveren allerede har i hånden fra `args.app`. Slås IKKE op her (resolveApp ville lægge et ekstra helper-opslag på HVER handling bare for denne knap) - ikonet forsøger selv begge former, når mennesket klikker. main.swift: Post fik et `target`-felt. LivePanel fik en "Show me where"-knap, samme princip som den eksisterende "Take me there" på et samtykke (hentFrem): MENNESKETS klik aktiverer appen, agenten rører aldrig forgrunden selv. Knappen læser `senesteMaal()` - det igangværende mål, eller ellers det sidst FÆRDIGE (uden faldbagget ville den næsten aldrig være aktiv, fordi `now` kun er sat de få millisekunder en handling rent faktisk kører). --dump fik et nyt `nowTarget`-felt, så test/status-ikon.mjs kan bevise at målet når helt frem uden en levende GUI (D3's prøve-halvdel). Knappens EGEN aktivering (klik → appen kommer frem) kræver stadig film på GitHubs Mac, som D1-D4 alle gør. Verificeret lokalt: swift build ren, scripts/build-release.sh kørt (vendor- binær opdateret, gitignored), test/status-ikon.mjs + test/ikon-menu.mjs begge grønne mod den friske binær. --- helper/Sources/cmcp-status/main.swift | 54 +++++++++++++++++++++++++-- mcp-server/index.js | 4 +- mcp-server/status.js | 10 ++++- test/status-ikon.mjs | 9 +++++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/helper/Sources/cmcp-status/main.swift b/helper/Sources/cmcp-status/main.swift index d6907871..7b3be878 100644 --- a/helper/Sources/cmcp-status/main.swift +++ b/helper/Sources/cmcp-status/main.swift @@ -21,7 +21,10 @@ import UserNotifications // MARK: - Model -struct Post: Codable { let ts: String; let text: String; let outcome: String } +// D3 (2/10): `target` er den raa app-streng (bundle-id ELLER synligt navn) serveren +// allerede havde i hånden - se status.js' statusHandling for hvorfor den ikke +// slås op dér. Mangler nøglen i ældre/andre poster, afkodes den som nil (Codable). +struct Post: Codable { let ts: String; let text: String; let outcome: String; let target: String? } struct Session: Codable { let session: String let pid: Int32 @@ -90,6 +93,15 @@ func overskrift(_ n: Int) -> String { } /// Live-vinduets tekst for én agent: nyeste oeverst. +/// D3 (2/10): det seneste KENDTE maal - den igangvaerende handling hvis der er +/// én, ellers den sidst FAERDIGE. `now` er kun sat de faa millisekunder en +/// handling rent faktisk koerer; uden faldbagget til `recent` ville "Show me +/// where" naesten aldrig vaere aktiv, fordi de fleste handlinger er for hurtige +/// til at nogen naar at se dem som "now". +func senesteMaal(_ s: Session) -> String? { + s.now?.target ?? s.recent.last?.target +} + func liveTekst(_ s: Session) -> String { var linjer = ["\(navn(s)) (pid \(s.pid), started \(siden(s.started)))", ""] if let n = s.now { linjer.append("NOW ▶︎ \(n.text)"); linjer.append("") } @@ -481,7 +493,13 @@ if CommandLine.arguments.contains("--dump") { "title": overskrift(s.count), "items": s.map(menuLinje), "live": s.map(liveTekst), - "sessions": s.map { $0.session } + "sessions": s.map { $0.session }, + // D3: maalet for den igangvaerende handling, saa en proeve kan maale at + // det naar helt frem til --dump uden en levende GUI. nil -> NSNull (JSON null). + "nowTarget": s.map { sess -> Any in + if let t = senesteMaal(sess) { return t } + return NSNull() + } ] let data = try! JSONSerialization.data(withJSONObject: ud, options: [.prettyPrinted, .sortedKeys]) FileHandle.standardOutput.write(data) @@ -506,8 +524,10 @@ startSocket() final class LivePanel: NSObject, NSWindowDelegate { let panel: NSPanel let tekst: NSTextView + let visKnap = NSButton(title: "Show me where", target: nil, action: nil) var session: String var lukket: () -> Void = {} + var maal: String? = nil // raa app-streng fra den igangvaerende handling (D3) init(session: String) { self.session = session @@ -521,7 +541,11 @@ final class LivePanel: NSObject, NSWindowDelegate { panel.hidesOnDeactivate = false panel.becomesKeyOnlyIfNeeded = true panel.isReleasedWhenClosed = false - let scroll = NSScrollView(frame: panel.contentView!.bounds) + // D3 (2/10): en knaprad nederst, resten scroller. MENNESKETS klik henter + // programmet frem - samme princip som "Take me there" paa et samtykke + // (hentFrem): agenten selv roerer aldrig forgrunden. + let hoejde = panel.contentView!.bounds.height + let scroll = NSScrollView(frame: NSRect(x: 0, y: 32, width: panel.contentView!.bounds.width, height: hoejde - 32)) scroll.autoresizingMask = [.width, .height] scroll.hasVerticalScroller = true tekst = NSTextView(frame: scroll.bounds) @@ -531,7 +555,15 @@ final class LivePanel: NSObject, NSWindowDelegate { tekst.autoresizingMask = [.width] scroll.documentView = tekst panel.contentView?.addSubview(scroll) + visKnap.frame = NSRect(x: 12, y: 6, width: 160, height: 22) + visKnap.bezelStyle = .rounded + visKnap.controlSize = .small + visKnap.autoresizingMask = [.maxXMargin] + visKnap.isEnabled = false + panel.contentView?.addSubview(visKnap) super.init() + visKnap.target = self + visKnap.action = #selector(visMigHvor) panel.delegate = self } @@ -548,11 +580,27 @@ final class LivePanel: NSObject, NSWindowDelegate { func opdater() { guard let s = laesSessioner().first(where: { $0.session == session }) else { panel.title = "Computer MCP — agent \(session) has stopped" + maal = nil; visKnap.isEnabled = false return } panel.title = "Computer MCP — \(navn(s))" let ny = liveTekst(s) if tekst.string != ny { tekst.string = ny } + maal = senesteMaal(s) + visKnap.isEnabled = maal != nil + } + + /// MENNESKETS klik - aldrig agentens. Proever bundle-id foerst (det + /// aegte format), falder tilbage til et synligt navn (det modellen kan + /// have skrevet i stedet). Finder den intet, sker der ingenting - ingen + /// fejlboks, ingen gaetten paa et andet program. + @objc func visMigHvor() { + guard let m = maal, !m.isEmpty else { return } + if let app = NSRunningApplication.runningApplications(withBundleIdentifier: m).first { + app.activate(options: []) + return + } + NSWorkspace.shared.runningApplications.first { $0.localizedName == m }?.activate(options: []) } func windowWillClose(_ n: Notification) { lukket() } diff --git a/mcp-server/index.js b/mcp-server/index.js index 46a44b18..8e317d5a 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -1836,7 +1836,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const a = request.params.arguments || {}; const klient = server.getClientVersion?.()?.name; if (klient) statusKlient(klient); - statusHandling(liveTekst(navn, a), 'running'); + // D3 (2/10): raa app-streng videre til foelg-panelets "Show me where"-knap - + // se status.js' statusHandling for hvorfor den IKKE sloeges op her. + statusHandling(liveTekst(navn, a), 'running', typeof a.app === 'string' ? a.app : null); let svar; try { svar = await iKald(() => haandterKald(request)); diff --git a/mcp-server/status.js b/mcp-server/status.js index 53d7b52d..3fdef9d8 100644 --- a/mcp-server/status.js +++ b/mcp-server/status.js @@ -70,9 +70,15 @@ export function statusStart({ session, client, version }) { } /// En handling er begyndt, eller afvist foer den begyndte. -export function statusHandling(tekst, udfald) { +/// `maal` (D3, 2/10): den raa app-streng mennesket/modellen navngav (bundle-id +/// ELLER synligt navn - begge forekommer i `args.app`). Sendes videre uprøvet: +/// at SLÅ den op (resolveApp) her ville lægge et ekstra helper-opslag på HVER +/// handling bare for at fodre følg-panelets knap. Ikonet forsøger selv begge +/// former, naar mennesket klikker "Show me where" - aldrig agenten selv. +export function statusHandling(tekst, udfald, maal = null) { if (!tilstand) return; - const post = { ts: new Date().toISOString(), text: String(tekst).slice(0, 160), outcome: udfald }; + const post = { ts: new Date().toISOString(), text: String(tekst).slice(0, 160), outcome: udfald, + target: maal ? String(maal).slice(0, 200) : null }; tilstand.recent.push(post); if (tilstand.recent.length > HISTORIK) tilstand.recent.shift(); tilstand.now = udfald === 'running' ? post : null; diff --git a/test/status-ikon.mjs b/test/status-ikon.mjs index 7f852f4e..82dc8df3 100644 --- a/test/status-ikon.mjs +++ b/test/status-ikon.mjs @@ -78,6 +78,15 @@ check('...og at det blev afvist', /⊘ .*Type \d+ characters/.test(liveA), liveA const liveB = d1.live.find(t => t.startsWith('chat-beta')) || ''; check('en laesning staar ogsaa, som ✓', /✓ .*apps/.test(liveB), liveB.split('\n')[2]); +// D3 (2/10): foelg-panelets "Show me where"-knap laeser det raa maal fra --dump, +// uden en levende GUI. chat-alfa's eneste handling navngav 'Notes'. +const alfaIdx = d1.items.findIndex(i => i.startsWith('chat-alfa')); +check('det seneste maal naar frem til --dump (D3, "Show me where")', + d1.nowTarget[alfaIdx] === 'Notes', JSON.stringify(d1.nowTarget)); +const betaIdx = d1.items.findIndex(i => i.startsWith('chat-beta')); +check('...og en handling UDEN app giver null, ikke en tom streng', + d1.nowTarget[betaIdx] === null, JSON.stringify(d1.nowTarget)); + // 3. Det tastede maa ikke staa nogen steder ikonet kan se. const filer = readdirSync(join(STATE, 'sessions')).map(f => readFileSync(join(STATE, 'sessions', f), 'utf8')).join('\n'); check('det tastede staar ikke i statusfilerne', !filer.includes(HEMMELIG), `${filer.length} tegn gennemsoegt`); From 391d9584e91f6e31ab0bf9ae80e5beab2f110935 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Fri, 2 Oct 2026 14:43:43 +0800 Subject: [PATCH 061/214] =?UTF-8?q?vejledning:=20canvas-blinde=20apps=20ra?= =?UTF-8?q?pporterer=20tilbage,=20g=C3=A6tter=20ikke=20selv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opus-panelets punkt (b) (2/10, canvas-fallback-panelet): den eneste agent-guidede vej for en app der ikke udgiver noget til tilgængeligheds- træet (spil, plotte-værktøjer, fjernskrivebord) er nu at tage ÉT afgrænset skærmbillede, beskrive hvad man ser, og give trinnet til mennesket via computer_ask_user - ikke gætte koordinater selv. Ingen ny mekanik: samme værktøjer, bare en instruktion om hvornår de bruges. Panelets øvrige anbefalinger: (a) knapErFarlig fail-closed for blinde klik - rettet i f22496a. (c) "ret READMEs upræcise citat" - falsk alarm, den faktiske README.md:37-45 er allerede ordret præcis; fejlen var i min egen PAKKE.md-opsummering, ikke i produktet. --- mcp-server/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mcp-server/index.js b/mcp-server/index.js index 8e317d5a..05166828 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -185,6 +185,13 @@ When a step did nothing - the next route (measured 27-28 Sep): asks it to show one; if the app pulls itself forward the front is handed back. - Nothing is found in an app you just saw: check \`computer_windows\` - the window may have been closed. +- \`computer_find\` finds nothing anywhere in an app that draws its own controls + on a canvas (some games, some plotting tools, some remote-desktop windows): + take one \`computer_screenshot\` of that app only, describe what you see, and + hand the step to the person with \`computer_ask_user\` - do not guess + coordinates from the image yourself. A canvas is also where the accessibility + layer cannot black out a password field, so the person - never you - decides + if that screenshot is safe to take. - Whichever it was, write it down with \`computer_learning\`: the route that failed and the one that worked. That is how this tool gets better. From 6bc27b731c64cc4510f4ba621b7871a3c830948a Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Thu, 1 Oct 2026 23:39:35 +0800 Subject: [PATCH 062/214] docs(forside): ny forside - scenen bag dit vindue, loglinjer som bevis, lys/moerk, mobil Bygget efter design-skillsene fra de gemte Instagram-opslag (Emil Kowalski apple-design og design-eng, Anthropic frontend-design, Corey Haines copywriting og cro, Vercel web interface guidelines), laest - ikke installeret. - Hero: ren CSS-scene, du skriver forrest, agenten udfylder og trykker Export i vinduet bagved, markoeren staar stille, en loglinje lander. Spiller een gang, hviler i slutbilledet, afspil igen uden JS. Reduced motion = slutbilledet. - Loefterne vist som loglinjer i serverens egen form (felter og fejltekster tjekket mod helper/main.swift, policy.js og en rigtig audit.jsonl). - Lys og moerk via prefers-color-scheme, systemskrift (SF Pro/SF Mono), ingen tredjepartskald, intet script ud over ld+json - sidens eget loefte holdes. - Nyt delingsbillede og-forside.jpg (kun forsiden; de andre sider beholder og-image.jpg). - claims 15/18/42c, release.sh-taellingen og sync-tal.py-idempotens bestaar (tjekket statisk). Co-Authored-By: Claude Opus 5.5 --- docs/index.html | 900 ++++++++++++++++++++++++++++++-------------- docs/og-forside.jpg | Bin 0 -> 69512 bytes 2 files changed, 610 insertions(+), 290 deletions(-) create mode 100644 docs/og-forside.jpg diff --git a/docs/index.html b/docs/index.html index 5c966f58..b8ee9359 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,349 +5,669 @@ -Computer MCP - computer use you can actually leave running - - - - - +Computer MCP: computer use you can leave running + + + + + + + - + - + - - + + - -
- computermcp - + + +
-
+
- -
macOS · MIT · no account, no API key
-

Computer use you can
actually leave running.

-

It works in the windows behind the one you are using, and leaves your - pointer where you put it. It tells you when something failed instead of carrying on as - if it worked. Several can run at once. Hand it a task, go back to your own work, and - read the log when you feel like it.

-
npx @agent360/computer-mcp
-

It works without asking, and it stays out of your way while it does. Works with the model you already pay for.
- No Swift needed: the package ships a universal binary for Apple silicon and Intel. It carries the ad-hoc signature macOS needs to run it at all - not a Developer ID signature, and not notarized.

-
+
+

Computer use
you can leave
running.

+

Your agent works in the windows behind yours. Your pointer stays + where you left it, a failure is reported as a failure, and your password manager stays + out of reach. Hand it a task, go back to your own work, and read the log when you + feel like it.

+
+
npx @agent360/computer-mcp
+

Want to look before it can touch anything? Start it with + CMCP_MODE=readonly. Setup for your client

+
+
    +
  • macOS 14 or later
  • +
  • MIT, free
  • +
  • No account, no API key
  • +
+
-
- -
-
-

New to this? You install it once, and your assistant gets - abilities it did not have before. That is all an "MCP server" is: a small program that - hands your assistant a set of tools. This one hands it your Mac.

-

It runs on your own machine. There is no account to make, no - key to paste, and nothing is sent to us - your assistant keeps talking to whichever AI - you already pay for.

+
+ +
+ +
+ An illustration of what the log describes. The real 27-second recording is further down. + +
+
+
-

Twenty-seven seconds, no sound, and you can pause it. Every line in the log is a line the server actually writes.

- -
- -
-

It does not have to take over your screen

-

Coordinate clicking moves your real pointer and lands wherever the keyboard focus is. That is fine while you are watching it. It is not fine while you are working in another window.

-
-
-

Press, don’t click

-

computer_press fires the element’s own accessibility action instead of simulating a click. It works while the window sits behind another one, and it moves nothing on your screen. Two or more matches is a refusal, not a guess.

-
-
-

One log, several agents

-

Each MCP client runs its own server, so a second chat is a second process. Every audit line carries a per-server session mark, and CMCP_CLIENT puts a name on it - so the log says which conversation did it.

-
-
-

Still shared: one pointer

-

Two servers driving coordinates at the same moment share one cursor and one focused window, and nothing locks between them yet. Give background work to computer_press; keep computer_click for the session you are watching.

-
-
-

What still asks, and where

-

In the default mode nothing comes forward. Anything that deletes or clears, and starting a screen recording, waits in the menu bar icon until you approve it there. Your password manager and an unredacted screenshot can never be approved from the menu bar, so in the default mode they are simply refused. With CMCP_BACKGROUND=0 these raise a real macOS dialog that takes focus instead. A question nobody answers refuses the action rather than allowing it, so walking away is safe.

-
+
+

New to this?

+
+

You install it once, and your assistant gets abilities it did not have before. That is + all an MCP server is: a small program that hands your assistant a set of tools. This one + hands it your Mac.

+

It runs on your own machine. There is no account to make, no key to paste, and nothing + is sent to us. Your assistant keeps talking to whichever AI you already pay for: Claude + Code, Cursor, VS Code, Codex, Windsurf, Zed or your own agent, with the same one line.

-

The long versions, with the failure modes: - working while you keep using your - Mac and leaving it running overnight.

-
-

Install

- -

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 32 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

-

Add it to your MCP client, grant two macOS permissions, done. It starts ready to work: twenty-five of the thirty-two, no asking, except your password manager, anything that deletes or clears, and starting a screen recording. It runs in the background by default, so nothing takes your screen; CMCP_BACKGROUND=0 adds the seven that do.

-
- - - - -
Claude Codeclaude mcp add computer -- npx -y @agent360/computer-mcp
Cursor / VS Code / CodexIn mcp.json: {"command":"npx","args":["-y","@agent360/computer-mcp"]}
From sourceOnly if you want to change it: git clone, then swift build -c release in helper/. About thirty-five seconds from scratch, no dependencies to fetch.
PermissionsSystem Settings → Privacy & Security → Accessibility and Screen Recording, for the app running the server. Ask the agent to call computer_permissions and it will tell you what is missing.
-

Step by step, with the permissions part spelled - out and the questions people actually ask: - Claude Code · - Codex · - Cursor · - GitHub Copilot · - Claude Desktop · - VS Code. - If an agent is setting this up for you, point it at - llms-install.md.

- -

Two pages we would rather you read before you install than after: when this is the wrong tool, and how to remove it and exactly what it touches.

+
+

What leaving it running looks like

+

Each of these is something you can check afterwards, in a log you can read + back. The lines are examples in the shape the server writes, shortened. Typed text never + appears in them.

+
    +
  1. +

    It works behind your window

    +

    Most tools click by moving your real pointer, which lands wherever you happen to be + typing. This one presses a button through the app’s own accessibility action, in a + window that stays behind yours, and every keystroke, click and press answers with + took_screen, so you never have to take our word for it.

    +
    "tool":"computer_press", "target":"com.apple.Preview", "decision":"allowed" +"tool":"computer_press", "outcome":"ok", "effect":"performed", "took_screen":false
    +
  2. +
  3. +

    It says when it failed

    +

    The most common complaint about agents driving a computer is that they carry on as if + it worked. Here a refusal or a failure comes back as one, with the reason, and the same + reason is written down. When three buttons could be the one you meant, it refuses + instead of guessing.

    +
    "tool":"computer_press", "outcome":"error", "error":"ambiguous", +"message":"found 3 matches - narrow the search, pick one with index, or pass --first"
    +
  4. +
  5. +

    Several can run at once

    +

    No session lock, no one-at-a-time limit. Every line carries the session it came from, + so the log says which conversation did what. Two agents typing into the same app take + turns; two agents in different apps do not wait for each other.

    +
    "session":"3e6f9a44", "client":"claude-code", "tool":"computer_type" +"session":"54c41ca8", "client":"cursor", "tool":"computer_find"
    +
  6. +
  7. +

    Your passwords stay yours

    +

    Keychain, 1Password and seven others ask every time, in every mode, and a password + manager can never be approved from the menu bar. Password fields are painted black while + the screenshot is still in memory, before it is ever a file.

    +
    "target":"com.1password.1password", +"decision":"denied", "reason":"background mode: this would need a dialog, and a dialog takes the screen (a password app can never be approved from the menu bar)"
    +
  8. +
  9. +

    The few things that need you, wait for you

    +

    Anything that deletes or clears, a message to a real person, and starting a screen + recording do not happen on their own. The menu bar icon turns orange and the question + waits there, with the whole action written out. You allow it with Touch ID. Nobody + answering is a no.

    +
    "tool":"computer_press", "target":"net.whatsapp.WhatsApp", +"asked":true, "asker":"menubar", "decision":"allowed"
    +
  10. +
+

The long versions, with the failure modes: + working while you keep using your Mac + and leaving it running overnight.

+
-

Three modes, set with CMCP_MODE:

-
- - - +
+

What you can ask it to do

+

Not a feature list. Things people ask for, and what happens.

+
readonlyThe write tools are not even listed. The agent can see your Mac and cannot touch it. Good for research and for the first hour.
askOne consent dialog per session in front of the first write; after that it works. Password managers, anything that deletes, and starting a screen recording still ask every time.
allowDefault. Writes go through without asking, all of them logged. Password managers, anything that deletes or clears, and starting a screen recording still ask - none of them is configurable, on purpose.
+ + + + + + + + + + + + + + +
You sayWhat it does
“What’s on my screen right now?”Takes a picture and reads it. Any password box in it is painted black before the + picture exists as a file.
“Fill in this form for me.”Finds the fields by name rather than guessing at pixels, and writes into them, in + a window sitting behind the one you are using, without taking your cursor.
“Export this, then tell me when it’s done.”Picks File › Export from the menu (much of what a Mac can do has no button + at all, only a menu), then waits for the result instead of taking a hundred + screenshots while it waits.
“Do this in Slack.”Slack, VS Code, Discord and Notion hide their buttons from most tools until asked. + It asks first. Measured on a VS Code fork with two windows open: 0 buttons before, 728 after.
“Log in to this and carry on.”Puts the cursor in the password box and asks you to type. It never sees + what you typed; there is deliberately no way to hand it a password.
“What did you do while I was out?”Reads back the log it kept: every action, in order, with the app it happened in.
+

If none of that is your problem, that is a fine answer too. + When not to use it names the cases where something else + is better, including our own browser tool for anything inside a web page.

-
-

What you can actually ask it to do

-

Not a feature list. These are things people ask for, and what happens.

-
- - - - - - - - - - - +
+

Install it in three steps

+

It starts ready to work: twenty-five of the thirty-two, no asking, except your + password manager, anything that deletes or clears, a message to a real person, and starting a + screen recording. It runs in the background by default, so nothing takes your screen; + CMCP_BACKGROUND=0 adds the seven that do.

+ +

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 32 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

+
    +
  1. Add it to your client

    +

    Claude Code:

    +
    claude mcp add computer -- npx -y @agent360/computer-mcp
    +

    Cursor, VS Code, Codex, Windsurf: in mcp.json,

    +
    {"mcpServers":{"computer":{"command":"npx","args":["-y","@agent360/computer-mcp"]}}}
    +
  2. +
  3. Grant two permissions

    +

    System Settings › Privacy & Security › Accessibility and Screen + Recording, for the app that runs the server. No Swift needed: the package ships a + universal binary for Apple silicon and Intel. It carries the ad-hoc signature macOS needs + to run it at all, not a Developer ID signature, and not notarized, so Gatekeeper may ask + the first time.

    +
  4. +
  5. Ask it what it can see

    +

    Ask your agent to call computer_permissions. It tells you what is still + missing, and which rules in Claude Code’s settings would let an agent walk around this + server’s gates.

    +
  6. +
+

Step by step, with the permissions part spelled out: + Claude Code, + Codex, + Cursor, + GitHub Copilot, + Claude Desktop, + VS Code. + If an agent is setting this up for you, point it at llms-install.md. + Two pages worth reading before you install rather than after: + when this is the wrong tool, and + how to remove it and exactly what it touches.

+ +
You sayWhat it does
"What's on my screen right now?"Takes a picture and reads it. Any password box in it is painted black before - the picture exists as a file.
"Fill in this form for me."Finds the fields by name rather than guessing at pixels, and types into them. - It can do that in a window sitting behind the one you are using, without taking - your cursor.
"Export this, then tell me when it's done."Picks File → Export from the menu - a lot of what a Mac can do has - no button at all, only a menu - then waits for the result instead of taking a - hundred screenshots while it waits.
"Log in to this and carry on."It puts the cursor in the password box and asks you to type. It never - sees what you typed. There is deliberately no way to hand it a password.
"What did you do while I was out?"Reads back the log it kept: every action, in order, with the app it happened - in. What you typed is never stored as readable text.
+ + + + + +
CMCP_MODEWhat happens
readonlyThe write tools are not even listed. The agent can see your Mac and cannot touch it. Good for research, and for the first hour.
askOne consent dialog per session in front of the first write; after that it works. Password managers, anything that deletes, a message to a real person, and starting a screen recording still ask every time.
allowDefault. Writes go through without asking, all of them logged. Password managers, anything that deletes or clears, a message to a real person, and starting a screen recording still ask. None of that is configurable, on purpose.
-

If none of that is your problem, that is a fine - answer too. When not to use it names the cases where - something else is better, including our own browser tool for anything inside a web page.

-
- - - -
-

Three barriers, on by default

-

Each one is a behaviour you can check yourself, not a promise in a README. The test that proves each is in the repo, and each test has been mutation-tested: break the code and the test goes red.

-
-
-

Passwords never reach the model

-

Secure text fields and password-manager windows are painted out while the image is still in memory - before it is ever written to disk. There is no moment where an unredacted screenshot of your desktop exists as a file for something else to read.

-
-
-

The dangerous places ask first

-

By default the agent works without interrupting you, and every write is logged. Password managers ask every single time, in every mode. Terminals and editors ask once per session. Quitting, closing and destructive-looking menu items always ask. If nobody answers, the answer is no.

-
-
-

Everything is written down

-

A log that only grows records every call, what it targeted, and whether it was allowed or refused. Typed text is stored as a length and a salted fingerprint, never in clear text - an audit trail full of passwords is its own breach, and without that random ingredient, a short password can be guessed offline.

-
-
-

No API key, no second bill

-

There is no model inside this server. The one driving your agent does the seeing and the thinking; Computer MCP only supplies hands. Nothing is sent to us, because there is no us to send it to.

-
+
+

Twenty-seven seconds of the real thing

+

No sound, and you can pause it. Every line in the log is a line the server + actually writes.

+
+
+

A password field is blacked out before the screenshot becomes a file, a write + action waits for a yes, and every line in the log carries a fingerprint of itself and the line + before it, so a removed or edited line breaks the chain and says where.

+
+

How we know the gates hold

+

Each gate has a test in the repository, and each test has been + mutation-checked: break the code on purpose and the test goes red. A promise without a test + that can fail is just a sentence.

+

Every change is built and tested on a Mac on GitHub before it can land: the + redaction test, 22 checks of the security claims that need no permissions, and the audit log + under several writers at once. See the + runs.

+
+
Passwords never reach the model
Secure fields are found by role and + subrole, because a password box in a web page is a plain text field with a secure subrole. + Checking only the role would let every browser password box through.
+
The dangerous places ask first
Password managers ask every time, terminals + and editors once per session, and any action whose target app cannot be identified asks + every time. If nobody answers, the answer is no.
+
Everything is written down
An append-only log, mode 0600. Typed text is + stored as a length and a salted fingerprint, never in clear; an audit trail full of + passwords is its own breach. If the log cannot be written, write actions are refused until + it can.
+
No second bill
There is no model inside this server. The one driving your + agent does the seeing and the thinking; Computer MCP only supplies hands.
+
+
- -
-

thirty-two tools

-

fourteen that look, eighteen that touch. Grouped by what you would - actually want, with the names underneath in case you are wiring this up yourself.

-
- - - - - - - - - - - - - +
+

Thirty-two tools, fourteen that look, eighteen that touch

+

Grouped by what you would want, with the names underneath in case you are + wiring this up yourself.

+
So it can...What that means
See what is there
screenshot · inspect · find · focused
Take a picture, or read the screen as a list of things with names - a button - called "Export", a field called "Email". Reading the list beats reading pixels: - it knows what a thing is, not just what it looks like.
Use your apps
click · type · press · menu · set_value · paste
Click, type, choose from menus. Several of these reach a window sitting behind - the one you are using and never move your cursor, so you can keep working.
Manage windows and apps
window · launch · quit · activate · apps · windows · displays
Open an app, bring it forward, move a window to another screen, close it. - On a Mac with more than one screen it will tell you which one it is looking at - - a window you cannot find is usually just on another.
Wait, instead of guessing
wait_for
Wait until something appears, then carry on. The alternative is taking a - hundred screenshots of a progress bar, and you pay for every one of them.
Ask you
ask_user
When it hits a password or a code from your phone, it puts the cursor in the - box and asks you to type - in the menu bar icon, so nothing jumps in front of you. - It gets back "done" or "cancelled" - never the text.
Show its work
audit · permissions
Read back everything it did, in order. What you typed is stored as a - fingerprint, never as readable text.
+ + + + + + + + + + + + + + +
So it canWhat that means
See what is therescreenshot, inspect, find, focused, menus, recordTake a picture, or read the screen as a list of things with names: a button called + “Export”, a field called “Email”. Reading the list beats reading pixels; it knows what a + thing is, not just what it looks like.
Use your appspress, set_value, type, key, menu, click, scroll, drag, move, pastePress, type, choose from menus. Most of these reach a window sitting behind the one + you are using and never move your cursor, so you can keep working.
Manage windows and appslaunch, open, quit, activate, window, space, apps, windows, displaysOpen an app behind what you are doing, move a window to another screen, close it. On a + Mac with more than one screen it tells you which one it is looking at.
Wait instead of guessingwait_forWait until something appears, then carry on. Twenty polls cost one call here, and + twenty images the other way.
Ask youask_user, request_screenWhen it hits a password or a code from your phone, it puts the cursor in the box and + asks you to type. It gets back done or cancelled, never the text. When a step truly needs + the screen, it asks to borrow it for at most fifteen minutes, approved with Touch ID.
Show its workaudit, pending, learning, permissionsRead back everything it did, in order, see what is waiting for you, and note down + what it could not do so the next version can.
- -

And note what is missing. There is no - "run a command", no "open any file", no "fetch a web page". Each would have been a few - lines of code. A tool that can run commands on your Mac is remote access with a friendlier - name, and if you want that you should choose it on purpose rather than find it in a list - later.

-

Every tool in detail, including which one to - reach for when the obvious one is the wrong one.

+

And note what is missing. There is no “run a command”, no “open any + file”, no “fetch a web page”. Each would have been a few lines of code. A tool that can run + commands on your Mac is remote access with a friendlier name, and if you want that you should + choose it on purpose rather than find it in a list later. + Every tool in detail.

- - -
-

What it does not do

+
+

What it does not do

Being straight about the edges is cheaper than being found out at them.

-
- - - - -
macOS onlyIt is built on the macOS Accessibility and ScreenCaptureKit APIs. There is no Windows or Linux build and none is planned.
Redaction covers marked fieldsWe black out what macOS marks as secure, plus password-manager windows you can extend. A password sitting in a plain text editor is not marked secure, and we will not pretend to find it. Use readonly when the screen holds something we cannot know about.
Consent is not a sandboxOnce you approve a session, the agent can drive your real Mac. That is the point. If you need containment rather than consent, run it in a VM - and we would say so before you asked.
Prompt injection is still realA web page can try to talk your agent into doing something. The dialogs and the audit log are what turn that from silent into visible. They do not make it impossible.
+
+
macOS only
It is built on the macOS accessibility and screen capture APIs. + There is no Windows or Linux build, and none is planned.
+
Not the inside of a web page
In Chrome you get the browser’s own window + (tabs, toolbar, address bar), not the page. Measured: 29 buttons before and after asking. + For a page, drive the browser with a browser tool.
+
Apps that draw their own controls
It reads the accessibility tree, so an app + that paints everything on a canvas and publishes nothing, like some games and plotting tools, + is still a blind spot.
+
Redaction covers marked fields
It blacks out what macOS marks as secure, plus + password-manager windows. A password sitting in a plain text editor is not marked secure, and + it will not pretend to find it. Use readonly when the screen holds something it + cannot know about.
+
Consent is not a sandbox
Once you approve a session, the agent can drive your + real Mac. That is the point. If you need containment rather than consent, run it in a + VM.
+
Prompt injection is still real
A web page can try to talk your agent into + something. The questions and the log turn that from silent into visible. They do not make it + impossible.
+
-
-

Questions

-
Does anything leave my machine?

No. There is no account, no telemetry and no server of ours involved. Screenshots go to the MCP client you configured - the same place the rest of your conversation goes - and nowhere else.

The same rule holds for this website: no analytics, no cookies, no third-party requests. The only <script> tag anywhere on the site is application/ld+json structured data, which your browser does not execute - never a program. View source and check.

-
How is this different from the other macOS MCP servers?

Capability-wise, not enormously - screenshots, clicks, keystrokes, the accessibility tree. The difference is what happens in the hours you are not watching. It works in windows behind the one you are in and leaves your pointer where you put it, so it does not take over the machine while you use it. It tells you when something failed instead of carrying on as if it worked. Several can run at once. And it will not go near your password manager. If you only ever watch it work, most of that is wasted on you.

-
Why is the audit log fingerprinted instead of plain?

Because a complete record of every keystroke is exactly the file an attacker would want. The fingerprint proves two actions typed the same thing without ever storing what it was. It is scrambled with a random ingredient that only exists while it runs - a fingerprint without that random ingredient, of an eight-character password, is guessable offline, which would have made the log the very thing this promise is against. The cost is honest: comparison works within a run, not across runs.

-
Can I turn the redaction off?

Yes - redact: false on a single screenshot. It is per call and never sticky, and the result says plainly that the image was not redacted.

-
Does it ask me before it does things?

Not for ordinary work - it acts, and every call lands in the log. A few things never happen without a person saying yes: your password manager, anything that deletes or clears, and starting a screen recording. In the default mode they wait in the menu bar icon - a password manager cannot be approved there, so it is refused. If you would rather be asked once up front, CMCP_MODE=ask puts one session-granting dialog in front of the first write, and ignoring it refuses the action rather than allowing it.

+
+

Questions

+
+
Does anything leave my machine?

No. There is no account, no telemetry and no server of ours involved. Screenshots go to the MCP client you configured, the same place the rest of your conversation goes, and nowhere else.

The same rule holds for this website: no analytics, no cookies, no third-party requests. The only <script> tag anywhere on the site is application/ld+json structured data, which your browser does not execute, never a program. View source and check.

+
How is this different from the other macOS MCP servers?

Capability-wise, not enormously: screenshots, clicks, keystrokes, the accessibility tree. The difference is what happens in the hours you are not watching. It works in windows behind the one you are in and leaves your pointer where you put it, so it does not take over the machine while you use it. It tells you when something failed instead of carrying on as if it worked. Several can run at once. And it will not go near your password manager. If you only ever watch it work, most of that is wasted on you.

+
Does it ask me before it does things?

Not for ordinary work: it acts, and every call lands in the log. A few things never happen without a person saying yes: your password manager, anything that deletes or clears, sending a message, and starting a screen recording. In the default mode they wait in the menu bar icon, which turns orange; a password manager cannot be approved there, so it is refused. If you would rather be asked once up front, CMCP_MODE=ask puts one session-granting dialog in front of the first write, and ignoring it refuses the action rather than allowing it.

+
Why is the audit log fingerprinted instead of plain?

Because a complete record of every keystroke is exactly the file an attacker would want. The fingerprint proves two actions typed the same thing without ever storing what it was. It is scrambled with a random ingredient that only exists while it runs; a fingerprint without that random ingredient, of an eight-character password, is guessable offline, which would have made the log the very thing this promise is against. The cost is honest: comparison works within a run, not across runs.

+
Can I turn the redaction off?

Yes: redact: false on a single screenshot. It is per call and never sticky, and the result says plainly that the image was not redacted.

+
Does it work in Claude Code’s auto mode?

Partly. Auto mode judges every call on its own, so expect some refusals. The tools that take the screen are refused: measured on 1 October, auto mode turned them down even after the person had lent the screen with Touch ID. That is the client’s own guard, not this server’s, and it is right to be careful. To use them, run the session without auto mode and approve each call when Claude Code asks; the gates in this server still apply on top.

+
-
-

Help build it

-

It is three days old and MIT. Bug reports, an app that behaves oddly, a tool that should exist: open an issue. How to contribute.

+
+

Help build it

+

MIT, built in the open since 18 September 2026. Bug reports, an app that + behaves oddly, a tool that should exist: open + an issue. When your agent hits something it cannot do, computer_learning writes + it down locally and hands you a pre-filled issue to share, or not. + How to contribute.

-
-

Give it a look before you give it your hands.

-

Install it in read-only mode and ask your agent what it can see. That takes two minutes and tells you more than this page can.

- Get it on GitHub - Read the security model +
+
+
+

Give it a look before you give it your hands.

+

Start it read-only and ask your agent what it can see. That takes two minutes and tells + you more than this page can.

+ +
+
claude mcp add computer \
+  -e CMCP_MODE=readonly \
+  -- npx -y @agent360/computer-mcp
+
diff --git a/docs/og-forside.jpg b/docs/og-forside.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b926cfc8848ae4e186ae0c4058fc4e77835c042 GIT binary patch literal 69512 zcmeFZbzEFa*DlyVupj|~Yw+OOxCD0y5Zq}bxCOT)1a}DT?(XjH?(Xg$V49qB-uHa= z-udR4j%S3GU6L#WJDySH>j9z-=JWiA|buSeT#vGje~>p1`Uq@51RlJ z8wdO4B@p08VW43UU|F}x)3W5Xxi3$OQ3h~?tz<*&A`i0$p zI4B56Fq5!w;9OjYKixkv0T7T-U_PGb0fPzb=7~N&-sI zpD_NrLGHh1>^)r!?Tq5*V2#f}i`1!VFXg#lkP$FAzA89ux>`tm?&Ay_spf339Y{!f z7RgmlxYala#-$YJl2NS#I-W5u13p0rDVA~03n`ij2ZZ`hFR~nUq z@?UTP`F$XrEe7rGxFdB6JAuwo*oH$(nFPe{L@Du;#_#A<%;y6Pt~^J67NOUZ<# zXogjgU2PI{Hl;|~%13lKfWp`JfcF4+fe(O>%3lGvLqtM945Td8ihV&;{^gwmqyefd z21PZJFDwLwuk#RJNo_v^?ATNP;E8n}s`iiApZ;rQ@6rMI8qsKB{TGr{6O>dF4kw^i z7ZEfSk)p_CrObSt6e1k}8tu#Gr6CWLVven3-t#N0*OzCZ@|7IQMrBcDE!;*z+y;zf zL(|SGLi)57XC+|Pnwu1-m78Ij?-k;4?dMzI)+cZs(9ri4<5AQaPav?wNLW@MyT0g) zkpGZ~6}Kea$y0+z&ig)8+sN6I##hTX=hi3aiYEnUkw7lP?G9o$X(sB-xYpnfhH2^M z$#5HX%-KV+k=JgJ7h%(G>h_Ju#l&tSuZk~W-iqBax;stArrYfG$j1wQ=}_R|)8`v6 zo56`bxm34}VRzgb?Wl_rW4vMZ{>Scm!i-I#0#bwD@_>*WEr(BKOaBM}yjG|zlN`;W zkyNNG|5@}Jfl5S*Zn)5FuMBtIyt_O>#B{F;H{PNzH+$72AKW{iC2prxZiOah1?l9SUQ^`iCN{PnB2=m@B|B~BqV4k!$jMRzowN#IK0x`Fzhk^-hK6-Gr8Z;UARo;tk70Em){sVu${8-{>ws z7;j7<#lLJJ+v6r3Y#?N);Tb5~bkDpmy6ey4BxrNA1H&ZytE`Fx>(foc*))u)vP{-HR5%T9NIic%GVZ zW?$&>V_0DD3?eeoSkDk|)1#s7(()a4v+;gM$k_eo(7`5Q+sS?3@aray?b8Mnn1qq0 z&o;O2s}s&QMQ^w+B|}t#ui6H`{81?&>$BLaQ#|?i001;bnxv8;mAVo}xCu!e3~0 zr;H=%dYFUkh|yE0@!0r2sO7UG(2)y-7^lU~oHbY_(Hz(zvtVmwo`slog;rpMEvOdCwo-tsBJtjlP~57EO!wTLnt4H{j^?k)m{B8FPQs>gUj;U-VU&mdp(5ScmwVxBlh4d zD3wYgGPSz+rirbR3ZB?YIjD&*6moIy-&S|3+}2_mxlTT=@j~*k+$W@*&0iiHYaR6r zJp+WDq6aVJ)OTDjLhfn09@F_a?hD!;pQ`w|-09Z4iOg1RnU3&JVjr9@#KwLZ6H3hQ zJ}AbnJ^7ppl}$WQNIP4lIu#?i$Q4_=Z~n>NKLG^*$g7i6*;D__FDidP!&yxn$8E$ ziL$R7IQ%#6uTGlo{Ru!}We96zSnOx|sGOCRiu0451FUc40{A4r7XMFhPv)jWZc-Hhc z_geXfwH!nh6?MOOc-AMXAAcvz%^>b`k;+Cj4etW)s@(KywCVc+TzRzrk}wRw$QTyBq(Xap~8^lm7} zz17Z0!JDv0nxOq*A(l6c`|NlYX=`jF6grh#of*gCgw7|D&CTdYjd5zMC)`{3c2 z?hpAjM0_wXqgi9}WyH(Uhu5R7wk1m^H=ydjTt>L(qtDyYU`YL?Bjl{8IHj%h#U({) zl|Uz@rKO8jhvt$?Ej^(G7$KGb)=SchUfv|~g-$0suWW6l;9T$x%l$LZ`n27qn@ahD>pEGAQ0L^LkOInGtiL=sc2&M6tT&j1NR$9<=} zf1|5EfMtHz+^65)Ije^D=|4yA%Uuyd=}!?jSj8h_f$_i}(|eDV)Es&GNU_3KF|;zec5z+4U#Hzu%)3pce=Ao!F@(RwB%bZdW_H-D z{uTVHKOMHS*FfIB5G==EH-3lUF**c(mCa@azsv%t@ezbWkvpH72>5RON2yl|okFZE zSW6bI;4~`vAB5!5X4lVERfDgX1PjrXFr2>13UsXmrmm?;Aj(=c?P6d}oi6o8% zTdJ)7Pk;}yP(<(O{1O4!cMyi~0Em)VC~b4xr7+-jD*!-DA;btS{)Y{~J_%91u1_jm zd-$%XRLwxDU1j8jmy0j5l)F`*=;j9vE(qpC3Ux>_et?EfToLZA-&;5nj&How=$+n- z-i+UP;S}K_{384c-iJJ#&^7P9*3pROksjm#b43k)6qaF97uSj1=~~8DD?BuFc?|`m ze@W3mlvEThNoYbri{5u^optfXOi_k#g=}%Z{M@{|d-gy&h|mkLVp@JqzG+uMQoe{d7-?qn z{!i?;Q;`m*FYL2jBiWR-j?6^WsDv6FI{|&A3g*qoTlf=qCzL2)>g(xzV(h^qEC2u= zl;nXFboG+qUHkO3^&f{}ue4KF;Sf z=Gl*UqU76x*n`;xwEbc@VLt1^xE)`7ejKVv$j{35)W&(+L{EOOik*d7?P12;9lF3$ zw?geUU}Bbvb&Pwj&pbM7P-bdSrRF1rhdsw+Kp_VIGwZ`2<0=>TM)$^|$qg2%j7Fp% zwfCI8X7Afa-TY&J_}q3ntzU$y=2FOXFFj1&R-2YEIk zP8TJ1?o=Wo80eqm<^OFU`4x55cvTH}t^LwRk;RH9a)3!wYMRaL#)^?0hh2*`Ab$pa zuQ@Se<|XrBHdr~E-hs){wY`Op+vhdc@fQ4IrJQcs&2NcP4%0b?ny<54)FwpG>>B)4 zq^txhz7Fhfv(^*NZ^X=57;@4U`>@D)O?6v0G-b>8m~+90IHsIzusT|ibGi|B`_$-6 zberCk^}YYY?#23Cj98u%_LXfndGqb9j=*V z98_O!e6YM`Y-ah9r}q;$oLqh761M28<}K`gibpXc8NG zXxx*ymHZh#Y+g*b?5t}?6_hU2C8d?yalbWt!&%OD1XDfSmGPYmq0V_G9q>onqwvdY z_lypt>X!vu!L0n@z`d~~9><@)dcAB#Q7&{8iKr*V;ue@}lQLQ0JG}4v#%s4DsHK$9 zzU&B=AaW9!Uum}vHro-Kb1oJ90Wd++ylEh+6{MYHebWweo#_(4TC=~ z{D5i5U0~Foz>GAUK07ix5KCw@F2fzxKr~|P2K^V_zF`@)p%6ZFL=Rcbl7(a-b(Un_ zivs>|aHlM%w13)57`_jkoePNO?tbm@Hr|AtppHb=a|;-@hA*UBTxUfiji!{dh;Y{+ zoh0k&zC>W8QLr1HO<=h7w80i44zo9Kf!H6p69h8%;e%NOiNyn*D}uz?gfx&>HHzlp z7^}Z8e1)bFc8TP@{u9l&{1`1!m@TFQ1;pLtimyqnzChlW;P<$Y{nm410XI0E)# zgYu{8<}iqxH3AK_qncxs%mi%br_4;~i_*9L^IeWuoLM#*_n0Vf>!4!xInCHOW=unJ zlu9SCHgzq;t;Ua~AMG9c3_HtMt(OnpkSZ z!{1=A;;2Pnt2b?Ba#r`@4s$nucfQz~vZz{-Kwih4+%(ccJ7CEN>08nj@hrf@DDz1- zPRsTW06)!tOrYj`Ss(p&BvTgl4pM(e>aTnMKFs>8hCY@6Z;umKXReN8E)d=MH_yI9 z8HD=Jw6X?@VHu1DiV@>jEI8-C0OQ|L3NBAa)|z##V^95J_x~%0x!?|`-&@*ULG44% z>_Y>F#s4A?IEO(JanLVbt0npMzp5O3pGw|>9yK!!SKxn!2&^!t*Cg}*MK%B9thLx>dx_hL!Cr3`qwWUU7@3$qd?W$t5(@~zlCrXsQDBh^S$_>H1@DT(fw$5jpdp_D zw|pnweFc#!6vZ-{@pV93)6J>>y~(0yfL~c-G*gg=BWE|$0UUgZ+1#-)cK@C6t%Kbo zUT}Q2Y!yvCb(itg9}tpr~sZauLL$-1N6LEO74`gF3-XUY1amjcm3JdOl#R}*u3 zc%z&Y~^`vv15>QhD`@>KcWHpnDDe=e0W_!)x6T5JAdJaHx0Pix3(K>8o>=u5>Ucf zxst*e%bh+0e7X0QENe81rVs4vUn0d)5nJ8^2+>%y^iGh(ohM#U8>Y6XQ;)20VN`}q1HEr;R;6|KPH zt{MGL4wlMoHs;621XvhTPrp1djfLT6${aHTv( zx8@#|^wT95c%NV0-*I|;=Ly@>J;P>&?(*7La~{$sp2J=?E(lKD$aU3}1zWz2Mym~# z^k6oWOu{Bdq zHT?`Q`FRqvXaxrQi;z_{OY8J(+gA|W=uT_e|5Sx%Q&0D_+*ScA%0fWqcGY2*y$4HC z;j)bp%u-U$ z+PCq^$8S%L7VOds^oEnT=bAnxWmX}qft^E1TuAH(X&xG(m{44d@!6g1Ig?B;VhE+)kfv{A zMYB+_C=R05Mt}m}F>Z=IVBKmb(z)i?@x^NSAZAvyiLia)jrti+?OvHAsdE(`ZhkZKDDy_W51 zjdWxKTB>GFF9InPT;G#)zAb3rT;E^fgfs|n4Yk~Va5UApn^x~M#z6LZd0oR|K2cNeG+76Ta$0zadZ)bvd--9+PCo&BD5p6ddSWtBGQZ-TxVs>mavqhk)A(zZ$G~3<+X20@Ra|5T_QOj&~`a zTW)RNQ4!EkDB9CsdYvQFmL0QOR^V-hH~XGIY%jk@Va9}Oe}Lw7E!$U$?_M#2V^OFQ zK_KkPiK*z}YZxr9`q%R*mHmzbN9EpEZdzq2ayJh-FIslrdWuRhv(KRpl~cSu1fzf9 zJ;$(C4^4;)ele$}ZS9s#|3=jRbTX%gm)3>98j~N1O*d7gYZ|Dq{qz#m|c$mU=MKE(joPSid#yx8O3< zRc`UyO(pMoe6Y!Eas-3cev0Z(E-PkSP_yQig&7G)?$HH15#lgy>TP15PJBC-<-p!X z{n3`$6Z&=7n|#FrY|0i4#qL`$K9P)!_W38sIGVK0d^tfl{$+F8oJDoS_%Q}mWS*>elo_m;A;>$0mrasb`{7CB5 zTgA>oO@3foI*H+cMw2O-)`&t~L^e5sgZpvcifZgGMroxv7sZVBglu+PU13MU6m1ry zveJ8!YSLi#zWdd^8Ev0O*66442fQsinx+=FUwgqgXh=zi2e=684kiS`CL#}-FRG&E zpUk`}L*offq!EoIuh6|9lrj*oC}!@>8T|T$_?K8`tX;xb$%>P@>!PL_vqE;&VE`Hw z3)meRC7`lsE~nxvXsZ(`jk^?qryjDT_iDqMyJd5j7)^+Hh0~)dliy|2Cm6}Ob)rxo z6;1g`c1ug^GdQ|!sABQ=&3CvQ6`f^62kQ%q>5Fow$a9Ozr*_H;A0LNPJgF>ZADs%+dCG)rrk!{56Se548Dhdfn z3q{ttTiv^#^|8|-mA`u{zMn0)x_sr@D)v%6Ue=n|i9pqQjq<(@H0fq1q$pd88litIqH>UW3Bc+?It zi&mj6R?{r6GqmmBiW;E#f12#5xqRE!-u?{0r**SX!3ejP4fk3$Rr1W_-eZ_xFLZYZ zC<{{^6F|f04o-x(`I594slEq)6qQ+EG_6D@lr?95z85&g2NPT!p`##h?O_yJ7i2m_ zU`Qcx8MR|?FJs6cu_sBRzR*P@xq{}dDnS@=&XjGkU&Q2$d%m8`JBXt$dQ2shb&`ea zB?t49{hm*P!b@@`?pmGAx-64AXDZ~WZu$#hpDBirPJBOtdhPVo1*`j@La6kL1bxbc zV0OBjJ? zQwS91bcFKBui=b7hz%4biG9_f${IMO8?U8P2flLl6r^qPvKWG%0VQbWt*>chCewob zF8OUw@?Io1GT|NCKn#N_SYJ@azHy#({$QT_3{dbK_f>nv4C?TG1Mh|1DH*Hw6;7VC z6lGoXt@<=mxW2!5afs=*pby74e@u?~>9Ul+zikpl4wQ2_ol=FF`pZMHSv%>xMg7t2 zs$}{<4XRz%uiEy4U0M1LI6Z8FA}!@Klk0eGT{f@!4&Hd!)nRUGgj~ zw{k_xF&C##$0_;V(ON^pt=%AHkHAfHxY8&o%RtJ>sieV%S?)4a+POy6xkXIAGmc1k z>;nNsEQLq`kSs=W3Rh9Kom{zKqBKrX>EUJ#ti4O_&1B=%AV=cOldXZrMuK1A{^Ows zh-KwQeds2bszFcB0B2j|Gakj|8-29_QZmZV2QbA~L+^qoX)Ex>%!g$|Ik#GKB5TNQ zY(RCPOP=0m9;!?c^fj+WQ-$_iiTO;m*#ht={(4AByQk9jH(WEo zaMifW5P0e;a7)Tr@-g+!ha2CYUAdx9xe%OsvW9(>$!?skQb||(U#NNtaKDd_SkqaEK(QJRbj$>Q(Hc@jCv0=tRwLEk5`;lSoY!nNhRCoVNWK#@OG7pwT0uONoHo-u z{S*u$MHSY&x4t&;Pm#^MV5W0hMVRoZTMlO5Hu$@`ydDFFi3+I^-*)~SQp$aehwbA9 z!h6b^c>1i~G7NYI8_dhW|GZ7=CaRv%vtuD zkOaB7R79kxILxvelLWcEl3b*)h_eA(E46?|P*HhCoDuY$F#r{xw4e~xo_uSGx5%<0 z;Cf|$cf$0t%Es6f=VQ7I_6O{?U-XL2QZx^zrmItJ1zsvJ#YmdCwLt6tyGda)7uDO6 zdp;<>U|Grv`TJKY--T8p-cDq{y4h!ygC+EJ$5#D?7+bb2#^+r?+LmU9eOtOA5c;C* zXq7wnGah_5OC(LkT0QIbHSZ1tA1s)Aashu}^3ZsR(Vj}hmx8%^rNbraYs)BCcFFWM ze|W!SWM1Uc)3RSIR4e3BPaeLBLM}r==GcaMHha#h?T=|Z__Q8?;clEzp++8BPsD!5 zK10ux)G$fc&}il!m#Z?{aZthc08*mQFxE9e1UcU_uL?!qvOM)BBjbzsqS{h#D)knG ztB@3nXa>|_{4;5&*2ySP-YeT>Q5rxNuQ#i+VRNJ(#UCRrx5!4AN#8yCBdK zs;JF9q+ajqtsuiJyn$oJ4r0Y5A4V$^kz;0aeN`6{nP3i)Bz!hLADJMjH=g8#ND;wV zx6ZP6v?Uj}`fy)Y$|=2qXn{7GDi~e=+B2`#plf1ysovN=t*-+PGFh|sEF1efEu~bL zb64+=0TO1W3qBIJn`lvTsR&TLVyckY99%?}E>4+;^|@>?Qb@F``kgEVSsx2UC}*!X zwTUlE1RkGL|A7y(06mL=l}g*sn<1)21mdBX_&r7PH~3lI$vBURR6uEyn(?di{{&JN|6N1J{B`Mq+mkKPEL zg7w!x_+br|QT3NPg7GARZou#_0oQCHGG}Nv7((;!H8>sazkUPdAv6x(_=NNZF$rTw zFYo|g0!H;4hj9c|S&%t+Ton2#A=G(W*P7kU>A}QWjkbBI z{Jp}XT962>nw+)ps3^3f2pc~hyp5(~0KcR*O?40;DKzQLTS0L;_wa)H+_C%eCQVI* zQafP|{-gSmC<#!^=Wth}-t@4CZj)4(bUOP$3gN7y&uv1ZQMiFM6ELYBVj7aSY|{<( zG^rJg&?re0`N6TIq3t%Q_=BH&=Ww2C@iFz&N<-GS8)tjpUCNv?C!MS5?CgHQ9wEVI z?%ODF+U$Du-LRdW7KD)0jxEVKmL`t(45%OxA6%T4a#ceKPC4^m1y3Ol|Gj(`Ry@7E zL)YS7@b{Fn$T?)hJMP4x`U~$DEv=i1WKiciQ^rAQc)~jih#fIRm_DeC)zw~AdDM|e zd|}$#wZuE7PFeFlX6veq_k+Xc!!GT6-<{6=+3dO6+x6N7uEf4c z(PXq9zv{inVWuTgdnL=A*gIDWMUkEe=qB(5R@_(LMiO*K(zb^BRxliRg&2yqs&Cg7 zkv4>jLrnBD74`*NazkytV#Py83U_t=CG*IbS3nrbm7jXo`lZ z%-RK-d`r{VF^SB1;KbBJqHovHQ+;s!>QqGvvC<1-UAyN*Z3b7P_LUuDEXIL z+QDYtf9_^&V~jzrC4>#lxqDhH?ooy~&YTHOAKj-j>9cs4wH{zKE)vHc>cILhbV}aX z`+lb%)F#=*XhU2UJ9q2rGn?JfdI+t8FXXE;=R%;(E{?&dYBTO%032JC{6!Y-{hS%k@a0E}er!itAxI`P{{zGjPn?T&umJ$zWBC zRWpOIaNvD()qZ`y=IU<2*MZeXSW+@PKMH4iaKFj3lfF^KYMrq~V~-*qzO9b#l_@l}>mlr?l> zZuRc6B&Or{tB?cOJK&@oY9{u^R~n{_%|)EaUf~UgvHHyrD2lQm*$_x9ri@xzyjMM9 z5U;x#&2MbMomBOV?-sm%;~n~F)bm3k+BYtz@=;mMZmWvEnA>_jLSLl-X`ZkEF+9G! zDygBNX0o?UK=C^+tC*y7zAWl?>aQM6mB1;ZjzU0h^}JAD3epoMc%~^yi(&6o)7r5R z^UIC5&T|1Vou0Rcv(UKZ=Sf6=kN-{Qsdd>gXRrrtL64!DhI0Rn=ADI9WhA`=-4D|0 z4&&DwnG$R`VVJR?cp7Q{p>nuncmhn9p~XI<$%VplP@#`Ko0uaglPB(UWevPN&}AN~ zeRB>?BG1_gv8)zb`tvlxW`P8;_?YGV5GitaE;dg8HY8R*8b&H1q+!7<5USBKjtqyk z9J#8T0(00cqU)`J6#9g!L};W8e<7I+Be){ag3(bKg-O7{+v{SU<3_|7cGz%AOqm${ z(+K$`ylJCBh~8*!G*t zbbr_rcwHNj2*&^s9s5X$Fy&qzf9F8;p7OE?!)V8%mC+~?DTF{YLY^6^D}Fmh@|*I% zDdwgu6Tb<9rS=+T%@xYJ>07OK>mq}$B~~dr!YrQmAyWIl%Rs4wj+R1xpC)i?by`|> z<|=3~R20>AinzMX(!rycZ#;K z+p{*}ZTHPW`27==Xrw$7O=>h1OnA86wCsc>sF{s6J>kMXafB%ghTmIyQ@$OfACN0& z>QF($fo2xAy9p_w%5b=**fP%Mu@pUuKVllq=vBSkLNjjQ9vi z8}*vyxQLPFaffrq?ij=_`w;vH-#zBPi)p|Wvk8@!Aedxtfx`BifZFH}UwE@b8o-+%g59ZA{+Ifxg21P?AT$}Cn>Y$!g zZYRYj$aIM@zv<{ zm`EC4y(3{pe?P9vP2Ray8a0HypxlN`zW5Bd_A{?Lw1$2?s@BaKNE#qh6-S60+5L*L z){53KvJrwLfX9r&@T$qps28;3FKuqfh#0J*QKp@>Y8W_%-%x@~MJg8TY`!dS@U1W< zctZr40C$5aqea@k$PE!7J-!|EK60L9z}zxh@5RQtFvpSNITCC2Vts*Lf^d|du_yS; z`}4-{;Dgra9|itR#1?w-o8vpJRuyZqb9+pFxMu(Y0ujbH?B(578ypQpOLH4WD=+k~ zuf)%i#t01{-;Uc0dD?RR24f(_CT)CMk(`slaPiTds11<=J(;iXhIk5Hv#lCoKmN@ zh{X^7e(m4T$?Y|{bqxf@;1}a);@5Io+pcbP(0Ikb(j#j!*BV;)uSIpx z_^2TMyA9o<@@fs^(~*~{vk-Av*fv)EmA~z~7>XYUqHGw6A@X<}MKn24d>*cT5f#MD zq8Dd|5pUQyb>C2?FDRhflhEsnc7@<#O0(N2J~(KyRh8oq4xJDAc112S%h#eI42l!^ zfcI~f{?%mE;`TdumUUa~O+2(g@o8y-(Ubu~5?_)1QqW5x z3C4SLZq0MtTD)HRXwMgHEB=;IWCIUH@Yb`KGijY(W`rhZxq$&%i1-*%B;vZWx*om( zPlS-(wghSl<1M!oS>)QbVkjnFM%`j9vp|9ddI_Vk;a&N@jM)X7oU^~dF{2oBrvvi^ zz8%+j#%6IapZwR0MLcMuN7lvutI_PZF7&xq1e!4t!Q`9x+NViz1Mf+B>hw8j$!>7s zNYN4^Z#?JOqz6T~w|FQRcO`gY@f{n(n-`Oj`F&p2&-f74ilW^d)~&SF?^MSCdhM(q zdmTR`ZcMFz8SDtzqr=1g1+WN9T;%og#y8iU1x^_2WDmxTL2g{H9iO4lMfA#dk97e) z3UaJQqP&F-n3b{AeUqCAQG4i15CIFrm=wE+2~B)-xN&_OLd#kvY}|}ZDBX>$44~-K zs&x?vK;f@IhK;p3r(l61T8M1xIX%qg4toZCk6S<>>L`!jL7EK%E<*qA$A5OpUabEk zBCW~>WroA9Wxs?B%hzj*X~5m)f1OvY;feOIF#SJRd;P~{Sxdlw`;q_nlHYeMo&nog z#Z~2Q&j7P4wC>VLj^VgQ`z6O`fI)f%r_Px5cFV-%4lu|v&pUCTIh4@G9>ZysT@i|W zIOkh{-^^L2>i2!t0JZrLAxeoxIIh0@P0? zbtgqRV2{t`zKiN8L$z#4?s8z}OZ?Qw)G2p)B?Duq{2_@C_;H0I;?Q348$>GmGXTXH z@`FkLog>#*-d|i$-&Khv13sA2&R~=k0rX9K0W84G_n4^p=9IlD;tDcl<97EK8>+bN zMxil?Wk9@r|3jZ-hQqCvgYeTDu}0U8B^n33g!bvTpF*E0FAwBP4BpdcXl zA)X@kKjkT8Nk(16fIaF@c{VmScJzjWburA_+5>a042Fl`C~~8M_iN1P-wqQK6URs` z+A!|L3nBP8X1#7UDdb-wkAy|=LYS2lE1|Ub-05^JCZ1wX1R0ff9wDRioGqdw_mqGnjyjS9LZoBR?>Z8F_)GQ8dn>sK@UcJpo3$WpCsBImA zh5AkH9%M;x-P~`3l@vppFfTQu1COr*-@ZYUs|!3MuEz-3UO_>_ z{zDX~5Rhn4C~x1r`tT7Nol!v-gUQaAj%J7xEBv$ zEOPQCE|Qg%RjL79ssk&JaT}8%fQ4C%u_Sh1= zuUoRUWQ%hojVj+HMq-8O*Tqd_Gw5#Tgwn0&Vq{tJHb{L<>C?#FunP$L33*IrO4x1? z)VJMJbqJd6=1IOiql#i^4Nc_38SQJQWI&-W zUXf=o*Zs;f_zTQT z-GOO-N~c0k1Hc7;1jyE)-gp@Z=L2cD>g+SX_N+G$Q{ma(zcsUOV&Vp)a3 z!EQ@XtPDn#m!Ic^iglDlxElpN2~SYO36(*mhDMlfF17*l86pQ4$ij@1I@vg;PHTU- z5H!^fge?}QROgtLP_o*I;YyeJLzyXC@VM-0uOt2N4RWahE$oD6H8mTl{4~GD4uptB z)(&mXOg8S1`YK9p-7x2(o-91}(+w(vY-s*(dbtJ$N$7vb+%v{LKf}8sB{>)diQzh8`XA&?W6} zyGOyUyv}>a1wZ7yM?*uFRaW1l_ipLMBJN?}C$JDRqw`Z*lbPnGN}wRQ;1g0t$gOQC z_R#oD1nGhXJkLE14%r?PL{Co(c&g+G8+yvG~k4q6e1D44iuPW5Y@iIC#4Zm0evvQ(Ai7}C( z4I^Y{I*Z+8?is?6CX|3L#V%ZXWS4=#@KkYnz2n$Byf}M6^D(LSvts4DTP2n!JkjUI z(t(-lMWoPW=C)0Z5e-jeMJbtbv;u5qZ^N@xIy|e3l5hnrCUYm9=k|CTM*JTo@Vc4D znoHlvuJhe5cnV8Y-aP|gQwWsEJM&U&Ut@8$;`}m`!Xy&$%iS333$CJnidQ!5Q_OXo zkgmUC$FJivI)3U&8NWBnuK!Q2Z^baRxT|LWjUb)TgEuq$dwx)a3su^=v{|O-Oqk6v zM&uR_@7LFaVSQhch<)Dpm55ha;>Eqe;7_|(>a~rpJcp4XPpTiOblXq}vcx%f{ODOe z=FCRXJrhIeDE4qz><;=_G!fPPp!__6=)f;=5=r|$6 z6NqotSn)+UK|{?Fc9`-*D!sX(pM8PZFDgqqLp4&%aD~}E6P8M+Eh+-F%=4i9gGzD* zLRuTun7(mpx2;IZcHf^v{r60(OYTWmevL__lo}G*p&8K<;4cx*(5G$H2S@?Pp8~9? zhVtP$6Bu)xK|#mGa*>)jYLT#q=@zm6Sm1IP_-G4##P(BBKo%i{p7M7DT2*u%h<>BZ zu!htwh!4XY0SSFHuL90wnns`keC~yfKaG&Pic+iTdvBmtCo5rVR!%$vREWgft6c*-kbCIwx+gc5|BcKV{s3y5BoIOg*eKht>G!2K0Fg@O__sZ z)rsG!X?DlL4MxZtkoop6`#>gGA2sR3Km>)kBgu-Wqbvd^WNkc?T8C4o+R_f%)iFf& zaK-XRs+ZMQXBYpXeoqw}@Q?Zd>ombO#lY^`8QhKgVfhPru?Bv3Et&IpEk3A6w=M!9 zszrgl!azJ-v1dT%duJftYFeA#^yH1BEtf{#IHV0o^uBHLeR3I*2e*hAgw@wtE^LQO z+{VY1HHi6k)un)d6$RX&L(UJg>1)ZbjmD020MYUIa3?IkbE4AS&D20jB6=|YMoX)O|DClciV)Oc;Kv=PE~LjWS+ z$r1I)%`+fq>@j~T=j0gxQBXn5<2%~)^^@bYw z)?DMTz=G!}f1|sE-|<#N>~i6#fFXOuQ0uAJy~_Q8tMnOA8+z+4afx-9cQ(9`_$V(^ zk`Vl4MX|rJ8yMewFL5)q$3Mw`I;kVB@}%^zKLc***GZ1GJt%&q0?_8E!|d?(M19M? z=N;5p`iMF9Rh-IM3Ay@Gah&{S@34z0Xv)&l&-ujDL^-A9mr>}EauL7hEfU@VtWx5| zSS`?uP367VK%i%%%1-EeA`%azk7Fs=nSxuZ4;kP_Zr0k`<~yFd4^^RUJpEblz#wl*AcV=qnbHJ5u^#N{A0BD^OFIrGu7z6iw1k z<0~j=3{3_%d{jO9b1ZY|$ga%NJyx5C%^PaI4wu?7E+aVj={*veM)ezGQ3>vOe0rQQp&xR zcPCbedUZIb^LRUnHRwGB-%B8@5B^UF6~x+!D(o$r%-T<#;rPt#jmjmWb@Oh6Z)$*n z5=)3hKfD1;bK24&+lsWUNmkP~iqC+Blyx1Ran#-f+R2Wd0!GSO1x^A)CFxF zcbwPaE5U~zSAo0*I+9q*phfl)5t2JG(~8^v(>%_TTOc^12IzNM0Az2 z(?SBs8^@MJ2q#|4|Fqd%=AqW`r@NFU3tg>mKiN3SGsHZs-%{=&t7g44cCeFY9lOvL z-9l~P;ZT}1^^KzL&qVFVPg>qr`MkNp)c`N~1(_$lzSbO3yu7C+$s4bEOp++sS#H@e zzHGc07h@~a#V*m|xPWQ*gcyP5Gr2ut_#&4nr%d&P-7nnm-pRSWTyG{Hw9seKPN*V978`aJL9ro{U~;Y5bzR%Bw2~_v9c*#eMTWV$1g` z$I%;F{wo(Wt8MBRi!P0CMbWfW!KaON#I#hn|)GIK`j|tH2s{e z!a$8ZZ^Tnbn2$`)z>MrX@T+pH-bWex-Q}1QMLgoKWNQpE`N8ueJb79g6B|@D&!V8! znVpHf{no+-p*YW?NY_s`OA^d}!=i)VZWJ{&q^t+~GS=Wo4-YR6tw<(efj;X^Q_a^K zV#YG}%FEcnvas8;iLeS`uxU`b1ehz;$_4=DHCBGqW zQAhWoAw%q~HIeT%am@^elgj_6m!cIuSBp}gGoyZZP!f+Rjp|1I49zPX`QWFOnUTXs z)uoj*4;!p$j68ADLgj2-ShYd~KmU87p|oFVrbUy67{yc=GexpyiYUKxDlUB!r1}F~ z0+m7bdXEz+d~qd38J0O=ucu!1QD0>k59P(aC!nYVe_pO`Twq>_BmV9(U@)ZMGK2=T zKgh+)sB#f}=6^rpLsc;qn#h4-3h-p~VZY;g@A|G~onAliIs~(2?Kmyr+7fRh+x6h{ zLf$51g%yuN=rJQOjzDD(U-@S|8?Qj)y!PkGE6l>~{f>tmV1oa;pQroRLXBkss(lG4 zk5W(L{qEz(*K2@wafiG`&lzATr5m!!tm6}F_3BCT2VO7FHTedYT9~P(MBd9&joz`j zyjCcO<_W=V1UWD68Aa$RF{5#fLEoRgCm4=jGNcaG479Vm)IH|-R@k2~9`-k`!TYa- zCOkOoK5ki==^@#!_sl}g1GxjrX|1&CRe_^oK-1k~0>Xp7+d zM8sWWgpUze6g=naJh+9~L0ei^$VWh$d8nEQ-U!rD3G|s5Hif+%7BuV!7Zb~Ob~!wi zyEYQkcB-Zru9tlGrH}RQmDSb0Sj<8DcY!mqy2?L8+{)QkBv|({jU#C-8d%*@T;_8G zetITyyP7n{m=K4s61^Vi3RSUp5orYf7Y0i;i#pkim32a!;>}d|q|yH5{?bH93gT4J zur;PYLBqETt;HSUOfFZSHRb5jZ+kVQCXIG>6W6aT!kjnP&$+EnSSgd4GWg2bhLJo) zb@=L-P8A3#OP@+SjpKzZS?H5xBu+0lKTVY-39^SAO^U`2$0&jD<6EaXJl!F(neyg7 z^-+XP)Z!{wE{(d9Ql7$DPN3^O&DbJo`#=#&0U)t7iW)gF&d&t{-ha|>dZovUW55 z=5;)9wfMtX0i@^smnYn&dm?vLk0rkO`o6_l`8C0Dy!RUO#ej=l6Zv zphzK@bZ7p*hB;dY}C~d)L6Schb7rm$LO`ES-msG}g_M zGlaLsaa)^Ob##%8V}^mfjCXTmgW}0x4wko9BWxy9#Z|0A z0R_wQ%psyv8}KiJ+=0a;r<&_8Z#o)=AS5p5dDky6qJ+n=ocB=5(%)7N8QqXaFK30;Lmw}8vSa@;m?locZn9M*9^sXU@*a11-JZK$k@ zI)8x2z1gl1v42&TC)(`AW2wuHg)T$}HN(VP2L;^Bz(Ql)&uw%y?o~4o%q+&R_mW8_YE}mzaeZaFWcj2q4k5rz{zS zHaA$dHr{wY2|mR}c{SW=mfE}iuPt0DINX1PI#5$%8_Qt?Sn*IZzU|3u-Pp6~SFEhzpx@3$Jqx=5S@^aseqRm6Wry>3_GqkgKdyW&6bs1k1P+!r_Z=f2fru@LZ zV2#}eAFoqUaJX${VV}JuAX|hkdfIh#<)vR*rm59EH}>Ok+9w0L97If>kTd@&4ou$@ z9fx3y&@`do^>h$sM8I|}XnvY+&lnZ-z7Kzd=lfByo3P@&kd$IiAtv#qX?lA{bN2f> zeJi#hus&Bmv|^kCOYc~E3argV=Fb4t%{?XuO0p;*l_-pue@L9Y^Tiv{+p+{Z7S zyMN)f3n5B6O?|)T=%+bt?KtMkc#A9j!}JQp2YwuN*TndFiAQjJ+qNzsoQoP+6oIYk zF(`%oKn%(tL`oJh3QR_kPw)2!wmgHGPfa7ZfBnd4OQGX6Z%!P)g=|}dqgaQ#4@xb) zB>9=AE({Y1V*HOwzgj@2K_6+U!NcEP$*lQD3GGZ^lZab}ASRDaszbJ|fSV~H< zrjo_rY}(DO$26e`NoKCq?KE=MdDT-=-uY|!;8$WyvXzeIh7{{!8WJRW13r@0w<`we zJ5LF{f<~qTlJ9?7oo-AwM8@}jXf`GNc|Y#OW4_KbB6B!yB4rq&)Y7hX(ywdu%Q{>) zC3gpu{ix#pvTk;Il{urMtw+T$WVXrBZ|)Jt`yc>bc?tjbhBr8C`|ZL^^|1jU*}Dj; z`FTPPu$jN}-jZZQk+3u&p0lmJJ$hrFB+wkh$~#lWJvl&;nGZL&A&=Ky#zyU&_yNv> zib0*^Cm9~y_%hA1^K}++>*JwKEH0gP0qOu1e3 z(=320Se+2gGA<=O_n5;{Vs?o%^ZPLZSLGL*jv0h;GmQ0+)|ILtU1tJ z3elSUr(Ui%_c-@oQfS+AxO+~3A6bfjNsjU`I!j7n39SsP^Vq3(ZJbpyD6;enP7aj) z9N;nSIyft}){Ye5qj>vnuc@|?^<+kYi|&Us7f)h%^fd0Kle_cJBI?#Sq@Rt<2Lni0 zJ1^Ylpf?o-oI-*uefqrmEQ-4VZ#7oSf+8ca0W|G#NR1EXQj!dh$f*_`iZmSL&y(az z>P^4$N)C+(m#oo--=k1ZDg}!~aq^hG!*kDCK94~}hBV9e>bLzkrtbWW@ zI;~2 zo0_Z|;KJMWm>Wxp)8O9OFA4b}Yxx%ryR0Mj30?1w{yZXnn4tQd!!j00s4v$`evKH- zS93E~LL3mDnUt*M1z~9eaMbtr%i3&*a)-Z}r)R`d59r8`L944u2LD!#5KW+D=?gZ-VUwl z{CGQzc3cn$?F0z52<4(;?))(bu>S!U2b=)^KN;^GioKc24F)fNd zipY&#i)d-Dme`tDE$Ug8tfxVxw?N)14|#B7=Cn9of!n^Y(?P^nml+TwLzgt`cq#K# zjAK5+&W@Eqc=#k}^)_z9EdC0k>}NW8%9Lrxgk{6~|-B+GR&F98v`|I2f;<#DnVjG$N&icP_TZSF~+Af-z&CkYV zvScetf(vD#)Jld(2o=J~5B*xNvz`^5&i6=quPe{T8itCeR|f8t=MnbHei{GQLxdTs z8?LZvauHGeQdGKQf4m5Dz=>@TGV~VhRn%vAoy@w%YZx{vZog;=fK$2rwXe)aFL;m% z*Yw)I+Kg(Qtlg^xXqzSsrf_ldV47MJa4;QCk2dXMq9PR}ONuS+yGeD#E=w7M^vvsk zc!VQPG?r4H6IW{T4GCesI@qkO)jF=6K`xCriny)6Iyz+b&emY2{Bl9UptNnpNurjQtnqb-w+_VCfsN!$43kq;^L&}+cO&;nU^O`{HQ@KmE?YdHtk`HG`M9&sr zgi9SZa<^a~DS5yR+7DHCANM-n4qKQXo<7-OOb`PI_wc@M61f83q_-!zTyIWWbFX)nRaK`j9w+q_n)13^^Dr2%QRinb0* zi4MALkuNun*PY`4@NMpx>r-YIVE=YwU#I5L&pqX;-7iS>$ISDM>2>s?)qb-`kmjq# zQ_0nxU!;08RKo9@)_@6&yuT3bQNSHv_w_0vGVr9hEqC`F#Z*>gOI*{NTg;BKPP@#0 zWt~u$#-$@T2cT+xZ!MU_pM5Vp5`vf97%3sQn5kR!?PxL0`)e`+daBdZf^P0)?uIazV1ZjJH->vY>v zu>T>wTH>8RJy7RT|~)qmwQ%^bR|8T6>_d&g-j zk%xddz=Eh4{Eq6;$7r-!i*4B4Z_~ z-y+-$l&q_F-NGwj#8lmwz;nVZj+P=_6(I|mZS#@UITnW)Bgdd>>{1L8 z3JaxHF4_CXYH#o%bnW6rxK~g2n0R|bYXSdpps8`5tG}bVg5@U49fGsrYoBc z3PX+Vzi_C=nY0lZ7Y|qit0ugLXs;14uU4H)Cg17n&}WaU+EgtUBHo(xP0arGIsb2u z3hDmDEqpx<+xW|mM!UeB{ga~2CD|hV+W0((4710R)sbb+M9-(uMkyM5+;0+fv!gmv zL8bI(E)jLY(F}jIZ!ax)>E9L>WX0$bUXInzyi?*KXSpn$N)-?gJQVWWyT_bdX=w-E zFO>Na-$X+Us-LarMWVdG7nNGCT*rn3?bi1TWuPQxTw_mpA!8uAjLp4 z6!L(+b^)z*SXPK%v<%=i4n+qAUhvT)Ue@<5C-Yu~8{0~I`E+5kd#Lu6Cq)y7YVHhS zH|PjE99`ktA$sP=VyK6RJ^!&$k(>B{b{N=1As}N-SFgdKfU*vFPY-PPjU7m4;K>XV z6Ni{|uL^(ss%K42>2(PFgxCnb@Q8319m#}!I5Y%(L2vC;X>X20v zmTyQFn>3BnGs(QQx}{6ttSWMeSNz}&n572lb?dew=f}xr27=5*{UtACf8{j}t@oKc zAsIZ=Z2$|!YYxXNS{muDENK2mqd6!H9eM9Ey%y;$)D;btY z9E*CI7xJ9^Ndc^ve=v|Q*;Kn=6kD9er2)rD_h?rMz?H`81l7$ACh4@HtP`o-XZ)YY znCf~1pIjtxK5gdyIK?}_zqD{T9pBHgI)D~q%hp;1lF+g38n#U*0Tg&EQLEH{Gs&6E zFH1?tTMD+?0>}&r1&+WHy;A8~T_nw1KPa0PuXuvrWXS6tIm8W~M19nQC@L^QN6Yyw z=4sdtZ&lvf`&<0J!+K3Pg8oW!dDr^$2Zdj-;mL-NBBA`6$Cz$jqkzTUAhk5H0!K@J zyYA1D;bo(lRz+W}rEa+6~Pro@+t?j9vBFf+s!U{DyXg;>>38|QB&^F9Z96HKgbM`%MMS%zyy;x@iJ^&KPTo6l%Z?zH zEyR~GSKv=hSbfS*Rwy&iGHc68b}FkWW8jWpD-C&8L)G0hMRJ^7wWzZZCiYF>Bc0^U zbS&CT@ogC{&yV=220FSP^Fyy4FH@VO9UrGr6G*^OVQ$Qb+?Jzpk{UGH*u$1+@c=bV ztf_ihPTG=Mh6`JazwOBKtZ*Bl4qI}8kGxSIja<$1L`ZXWD3y+bNKUUZ4VwP1pgtsaJ6I;?-cLEj$Gq2zj zX?0p85+a7)Tp`_3Q_??nQD8rJeY2ny-ack_lz1BVNY!+k76R?rV0YKE@-#bHS3GLS zO6v`iyN{2!K{_^b8t-_DxLs)2sEz#`fae7x67pQNM6snhLOPS#zZZYey z(h4>u$)@6WF=}2*Y+RA5eOPYPSd=H9*gsm6qmI?8fCTmg$Ou}1f135v)(-xxqNis|?l4vvk)s&1S?hE5~w3 zHuH`%>D2%}>ym0LSfz@jr_W9_Mt@7gpKH=+N{1QadktP8W#$U*GiGW#&W%?VL!^U8 zyfqCL(kA+Ecg9`+K&@W$v@wF_F?90y-4+uT1Zc7u3^~0G|qiIcF@mty?jv}ea`Rw9- z{TaD4&T5ep&Deq zdRCEjJ-e+1MfmBY^BiG(13s3vOZO`e^;H~kxClDLc*@~R=PUPzN~)$1cv6+%ok1Cc zFPW*GQ9ADzt7xMlMs;ZpA+b}+;40LbVv9jvsk;y!@8@;p@Vz)?v`1Bom5W-{yV--O zFFL%$n>KXIOz=}_7{m7d<1F$E27C&o;Nu4$j;&q1Dy^K{Kz|r&0D5g7D(2<+@BN%AUMLizB?ROJxm>)hz&auQ z8IrzQMJkcTdnAI$a0E*HDXnQ9+Iqye0?+T%cz zp^7cf5lkw^;~{D410I@F0KH2-(}8TT>zZEAhVB|$+OiC|jAx|TB|Ldp@P#Pi8@5W_ zJMp}@tEw!ihkLsfB(!O)5hRjs0%J=_N;aY2`4CmU@E|G~k_~k*Vj69ol`?(`rLv5y z*$Y^I#5kiVndv2RvljT^Y3t;@G_q`Z+LS4S$hXz`iww;iMt zH%G?U!C0`NMK%(LKqzL8=*+S&4GjKPMH-y8L$wu`c z)>qXYaBd$I5M6q*I-9<>?m1kza-4z|-x^ffEqj;jFuR3k!JP?Ty1Tk+#;4j7%eB-o z=K|*VPk>R058fP!z=-DeWH}aa0uycNYx?T;8I_&e$(@&O9q|r0HlMgZM)89c(Pr;Z zb=4MGGP)z8Ug#iSRf(pVqbNbnQyK9pYXQqU5?}aPn_sQ0oAwg9t{{ZdPt|jooXN^# z04PtJcU|=WM=zJv&u09t;0Xmup6NXIWOM{H8kRy-L5{O4MY?&VWJK&fw){rR3AFR_ zvPK@9bpOV_6vr%x+t!kb?WYRU&+rW+iK`jiI+?2Ip}C2IsIv|1>ua*_3r3M=2FdQ9 zEYH}N*SR7=kqC+pCEi*C+5^0hf4qU@-_tzzeh9tOR%Q(Qd;v{eP( zDJEMKdOucGL~JtM6?bBEX0~S1_n#cavFIR0cAM66Q#66aA^$)Pq;LfST)S6|j=9w# zyjS3F+lha~n7U$n>c+68`G1>bNna0COH;tPDlhhudP4FYb%AfZ6*CKcZ8@X0`?iNUUBXMTB|ysaJzc+|M>n_GGnz@N-ogY$0VP_;jR;+bLG3Dd zjlm7+aAx)-XQIiK*ih1W@>}%S@EvVUmhzQ}@200b`N9S(mwuk7RLfD=;_Y~m_g!rUVng>XT2)$uPFQmWoG~pm| z&JVQd0Y%%21OL0i^s-ap#^s8PnnUq~T$x+7%`$9=XcKF6@pdtvEJcDI+CL7_AzFPk zpT!S~(N36S%@8#immc40b=W0ui9Bge8nYiip6mPzw_MNcO!r-jhD|;%owxffNt4^` zMPdw;Mrw{GWpKKT_xh@YZ#yjbOQDR-IkbT4gR7kB$40R?cz3fhc%{Ewv~hD~lK2PS zjSIB!1Gc`HTa!;jqi?>CpqiW&ZRRZs5m1tNLQac-dt? zzB9EI4<#LnG1Wr|u~RXt2pJ=gpRS~FTyL}5qeH}EMI>7)RV8(_{D2X&M|m1%B_!VpUqg74-`b>`jEIh74Z*X0Wk)muppvM$?Z%=(=_R_EJ?!?-KSb&y*k~Zrppa<2?(Hzh43EuY~sN5`L z>+YLRrSEC;zP%-i=6#v7*u|A{C|vA)Noh({=C))A*SdC%Uy`r4pizvc5kzOnFOoNP ztrdX->|4Y_H<@}<#00IDIp3dG=Iyss+*T4Pb0GfoIK5NTm>&+&92z-S0-P?($Bo8X z&vJz5`PP5%&Ey1%4xFeRccgk@z47Sn?jtJ+0rb9co$~o9gcP4f6RX;+X>mSCV7wmT{$8CPJ`my|Q2HvbZ z{0}?Tao;5srCTP6d@m~hNC@JZ{`YU|raqVe4v9K!XS+NmKhKy5uO>|w&mlX0iyj*U z-3XTFst(Fq9G?-4Lk0|TWG@QTy6uZZagDWfJ%#0iA);xGtV62UhsE*b~Ci?@{^fl&oGQ zd=;bLsKyh2{g64V{E9I7{c-Iv(@&oL1Zox@vh{he=({+255UqiwXh))<$!kCR&_6X z+z}S}lUs!KGj4zPlh~7ljH89IfD%_(Y9lBmnw>Fz`pqSIv?V8V;3O5}%0MM<;=Q?1s*sJvin%~;zg)@_BHMG}0WtltFAO#*IC_dkar>l4TvmePSHrs_N5-2QxGo6(&9*795a zQ+$TKt9HNph@u3xr3homD{nOIR}Z2suq%xdgDE#O=0{A?&R6)%G_Si9iI?~iTm+o`*v)mypDiw5=PA8c9dWtMftyu$fa&jp^%?LJbz{8qc!Yf7ti{rnJJ@S7`1#`kPK_)6eB zM9l)nvR|R3o{8qTQ|+TsML}~vyIkr$zra{1nqv978WVz!%7XIMKM2u}MTi?4TlKjg zY%|rOiY}f3rmkq5I1!gF0B*-|P3uCp3VA8?s_8h%eh4X#1DCy*$e}RFK|1)fxS}3U zyHJY@Bdi+?(FmAxv3;j0b{WDuyCjIOe8%~U{Ycp&R8a~?RZvO>G3=mJn*}-9QXKzJN3UsUDkIP>OTORtHjk24&B#P)hxq!*9Nm+i z2>qT|(O}hcreENQPV<0lXwFaCg-+Ec9B34+qtr`JODepaI;)I{?0-Lup@>}+5M1DK zSmyYirYG3+5mZ7R!xb%|qM3EZMVsZO<;y7n4j_-H`hZTfkU$|7^`)7bBWv)i$R@i% zGgK_zv{D*)H#e%9E&V4!3Qc7v^4d*>+3lBo1sy-$ja5GkKnb-NA#!fZsBDQPHI@60 zby8aR6}3N%#+Sb~2~K@o9>aNJ)nl%&+=?dNqU)R4G!VR`Y%Bda&x$(l*?NbIzIQ2N=syeQ&P9(1L$@J)nN{vrcI`BywlSZ0B5im- z%mC6$vVC?Q$PLfk0XMTsGeHYjrScj$I-}lQKY!HcJ1JbGOP8WCceOQ%PVA)&oA9;S zl4nZWZl?jC|L*34Y>0n2=OfK+ZHbH~8gZ`bvpUOWmE+Y%Y{}Nrlry$1jr@;t)xC3oC*!8__W_Mr=ySvMLe4BD3mJb%+v<#=5On^rdy z-|J-%q{4Q_vorYeqLKkYctl5Ad)F+ zmf){eVUD%QkE*NL=b|m8iz^{g@a)N5(mnRnZm=$mh)(b-*&MOY(IQZe*2G%0Ld+G= zMsVCp`IAj@ea1KNR$ts6@AryK9JS`!y1c>y7U4Rnl~`vqnwq1&&WT%E4~1sc!mHFP zwPu0V?XN@YR^EsDA7e?hF#rr^mub+9P8vOSDVA(hF4P zI#(s=CVQ(sEY}GiqSfi=SsDxQ>zAL({Qd-3__F_Xcx(}%_8O&|E~jm>01|S2A2utQ z^vEX&KUg}X?NEa^?e)NgRt2KEQyAAkBe3wSw7CRHT&Faz&72o z2A-A1Q17pn{c3Yq)6}#+ciNR>H-Bn95?Hd~4gzOy&9fJlsZvPF`pT!HIzu#9s%pPr zDcYfbDVW(JKpEC^c-UBmK>%-UZl?&2XD$$&iJ@>^c9{z-vH1PwA0WH-Fj|qX0`i~) z&f%uhYRr#EO$%Z48I>(*u+oIQoU}$dfdCm2ijeO8-HcVNwtqJ~DzJ@Mw8o-0L@3Ke zJ-wruj$g0gkDM0>`c#59DU1`7ok_!Ed59+*iywxkU4x-3WPp2uFBBnH6?+MC?A%b5 zY^8def_1ZD* z-nin;Z%SiL!U?p-EnT;t7E^WO0i01QwKFY|4pGD9r9z%-chf{Y?}Rl#c=kv&1E%S9 zf%KeqV>~52qMk%=(C$>AM6$3T!>ezpCQD&+b%#*9_2KX};Nl3iWR`OX$8%kX zGNJaCC5{x{*+q7GH=ZW?I6bLl2F*MqCni^lCYo-Pj8;@AH|k5S$>k7TZzrNyutCYJRd9&3k$R-)7%Ps)&n6Op2x%rG-_G^Ngc7LjoPuw!uj-b!v4^H@cDMSa)p7NA(Ux)iTm&XhV9_Bri%Db^mdi;iu7u98m8-xl0^% z<9GsO%D;1z?&C2@4PnFzfJ~b{&4B~#iXG-2CAjUtAF7VrFd2u0^4=s$m~+S(z@B;J zFAhmrOrUixO>@p@yT~Igm|RY%#ddxjd+RF}0+rrXY^jggs*Ws89E5_2Z3_ANLj8sb zpOJ*R41*lt`L1NhL2`eX`LEqdLzpBMUflgRPCcI9n=9`I1?f;A^@t?9g{_`hSYU@4j?3Swf-NnZWrX9Zl zI21}7ueBb(8ogrwz{^yeFbWqzb^C)bfz^As>FxI#Z@yzmQZIm1sSV3-Q&yjih6Z*( zP~R|6{tE|wwfY1`<%zR2;a>P_RXKlEX=1C&zdK9|3_EQ2?4pc3en zF{J{dq)Ki4O5CzD-mN;$zYLcNux&ugbBb-Xw1K%88a#RW%%i2_TM92zzzh*&+NzP4 z)+Y>84$2qzS`1Z+%O*^nZ1o@#91`_I+KJF zMZx55ZfEsXk$xjJYyz+OuFy{{#d9%jXb#XdSr)|pM53rwqz|ot zG$zC833|xgm}OXM0Y1+5U8Ba9Q+3wQN)9`+83ZAGKpJieL0CZlU%1bK$DNRWSYfCwX~nbqZwK1) z|H4H-&pFx@B5N(dpW)f>#O?@$G)Hd^x%O#2ESC;5zbdq}fDZd&+gq$|$xALp07Q*^FkP!&Q4zFp6qv%=4N$G zF>S;SkGgrNm+^n$Bu8xRs*Ft1U0TUcOKLBfg}*gn^Glug|3LY4s!`H;Dd<*9N4cZ$ zyo9M?DVUV;wOuRr8-bMIYe*O;m(}082jvhX@Y` z2M>ou$p*^|bMe8^KQ_C1>Rhz@BdgNZ_y2z1DG|zl_MOTuqSr~caxI=O{4Xtv-hTDF zY47B<1*#f@b+J;1)l(`bCyLk+Y?Eioe;Wiz7gB|f=R%(_!j%D(FdeD%Oqu>{XD<;Dm6K`h!(`gLbYi3b$ zgCN9IFQpVW(p3)3XKRYY zcxzKGIR4BxNRElDi=-C+aQ9>BRLpPt^R?4-pK#E!JPt7E{fvuOl2J z-XfHrI!fUJm)W*)9%P?^-ELoqN{AEQ>0_PIO$uSJrp`Xy!+yqzzyk)Uc^?O zW3Rmt9%i5%N}#1^6f*bFTJUuw(UF&jnnkF+Ew`s!AH3+|O93f2qgNZ>C{*Zep3p7> z=u{86g;egPoc;3cx?0vcyd!U1#LV|0?=GQ4t(>1{{*@SpRO{z2y8+Fs<0kpS#)yoa zF39ZxJjoab%R2QR43FL+F*)V~h68CSRdow={VW_}LU>G}NGKe-5DV|oj~+-q9S*a6 z;-TSvry=xCtR;MXUy?#b5gL`rkiur(+q;5mx&Rs+++ttQ=I@uuuTaS04L_%GVJK~FI|_8-l|C>wlRK9y zl=Ii^Jnz%)^I`H0DyTTcC!}p_hBNjhKo|#9BMXVvlRxP-J3TpbaB%dP2TdTG2QqFB zd$4Dq6$Wy2|JfWp|MLob(oe5IWL37ORP-~tPZ4aFVcZJu)5>Y@WtqBsn6LC;RRpXtCjZWQL zGHUoUEc>&i8|mv`GhN$(f;k=f;Vd?2myty>^s}-FVQ=JzeD_fv4H$3rOy^4u%<4>! z-%TzecU5m;?qK*EsS)GfOJY-}2W`JX zu*Q2F{TvIz*cV5YwN5# zLYTb3T=DK}k6V(c7#&@>ixkBYSM}bseWnLxgV-`ALB)P$v>!~MXq{xt)47bs+Lv2W zvM{eRi|BNK$F9*!Um`>8bYqkOt)D=7WToHK$DBZs2;XG#VK0Logg0zZ*WqrcG*{n6CDI5In>;vp z9=x{%YyaaID7Fr$Iz)W*+gyHR8NXk;T(S~aKnu+JI5ckqKb2LFk7bOcnz2lkl)!24 zzsKO*X5qAKFR|Rg!GMJ&04gT^hoa{Xgi`LDce=wgrVL9g#i6C3E6lukl&&>FdSttB zTb9H^IDfWG)p1KZCxH-#lzM6wzy~ohbO|AA?1#mwNqz7;Ej1E6^z)_=L8P3KZ)d-g z#h980;IP#K1FlaKjw#9b1Yv1j=P8#O@1jG?Z(63dahzwq$@lqYZCwdl;dz2T7xCER zwX$EluI@NOAk6D;O_bVV1Z8wIgD2FFIF0$_-^jg!mA`C%bsH}%ZC1dqBI-VC^aWj0a`LGL?D}Vm+1YEpqar<|$N!a}c z(Yk=A>slvfq+nCc05z#e+1A;!GmQ&|r+^&D;Z(c&H(LxQEq3NC7zgPD z{YyCtUz$j61qG>FtDR%Ck3YTP+J1yNCN7o^qF2Z89K^B>s(F4j>jMfPZgNJ9>Wk;H z2Mk5k7`tbr%V_m{#2mH{dY=YVoOP>lJ@x$RO_HWpKI>EZ0I`Y8JttJ^qBmk_8Glxg z>WR18Q&Y`!v`K%ozFTM-<)BtAHM1nSGV{d5t0WJ!)Gg;rlhy)M-F;J1*`_u~9}uMD zPG)aqejLTimCvaR-o*NLtF!S7sL^rx|1*1FqE_R$fc%gNE;6E#BBe74uB zw8U2@!1>|5K{bkeC!81^C#`|N{id(>2kDlHs+Th@ zw5zZ)yi6^YjBqiwF*pe{mh+!w!yx$+tQ;mmLlPwfAB^{HN=ZDAKl!w}TNOPP4KW0S zJ=(i_>ZQ!6)7v-rUnGl5)+t{IL+lP6`Yc&CFrAe~tP1Msa6LVTO_bV;zJ2BII%2(f zVqC2huJPZ5E7O>Pvul^C-IkZiFmNDQmi2n_Q1e9TAc0u%?Nc=at0pYchb-#*#f5xu z<|}6_1cg)`csS^>)c+_$(BDmcrMLHE?5j(wGVsqN`8kRNU)M!aseE!Due_g}hI|{h z-m#8%UvAhBT)YlFdrHCOT=K0m)rLG5wdSn-g(H}!eAl@D=2+_nP_^*biDenX#VZK?rUD<7Y9)Px?TcK!!wCr z((oIohg}-*#QiVapX1lH+?Va7KV3=JvV=j3YFUAktjKnw{(wr~O?6ug!NGGTfRY#r zGkj{TlaRDAYe9m5G+K)81M{vW<@Jep+BYS|jp{RY93$rMA1mHaqqH0{Nc@F!@TyHB zgp8hCPuM(uA|D=IS6pN}@~UW$g_=l|I(Hj8m8&W;pfLHPaK~pQS1&5$5T+HEqM#+_ zdhhe~ns|xC%sp!eMOSaUnpZx_lSRQ2*Zt_8^kq0t*(|xI@S_3OA`W2oxy+Zk%p^@b*;_#^huMv)<4Pw*fN!b}R@2-n~zqAifI_sy2RI6bbMRX_J>rcPp z%rfM}A!hQ774eod2Cvdi$lXrlYz4S85L9pL3;fB*{f=={%DgkZ=%5|@2MIcR!$;on zVgJS~#y~Ip81+!RP3YNFWd>p4gO!v~9kq5F-_!=P7n}2I@JS^@@Ga{HAW6}>av#I# zE*}M(`-ElWCF)q|YLZB5y<7v->_G(d-&C^rOSI(TB2n*=&?A97H%)xD5--@lCO)l> zhMx-!W7kZ#8TBnyxR(`Q@-9_NQcVbsuj7Xn2gET#uh9F9PAKOsncun{Jx7lZHtUVb z_Fp*iuM^$OTj|$nA#7X~wFkwAA)A>^-~B}=KcX_~pQ=(d)tmJo-g+uFY@N(JnhAL^ zmIs{IiSFpMc3lJbKFAPyNLDSL|906lSW*4uj>hXTb!tBdx_P>mQX@OSU#D{}$YoBh zL4ni2S|>f(p$Sk*YZdser&4_N2ZOb>yF%=$(DA&uGiSs-x4_%ODPphW$XfnNdsT7pAxB zRXm?E0Y6oD+QeP6!Hs)delOePSxiFa-NCBBC)sF|s;eZ$ zvex>G<$F5=YR={>^2$>N-)$Stj8N@Clkozhcex-G@^(}1&H{H#-zkUR{DZ-_$exq} zroo|4lSutW2coCQt~0Hczl%A04$FZYC-(~ZTb>*TKMDNUD}81nqsAUvcLk;dr7v&3 za?;3zM3`cMU1-@ld{9TwWKheTbASGy8XS9XnvMvew|S?np?X^P|`cml^WO}3U9b85HdPHX@Uh7uY}j$VRzZi zRb3IKEJeu=G7I_?I??22lKe(w!`M7>2C?zS;C?rh zJIA;)T^Cv5CKZ%Q+PS6SF@)u!8oy*=@k+0`;n)aupUr-YwtOEN8+-V` znpA~pd>5vTf^Kd8ZrGMA%`?_ijo!_4xV?LsX&jUlmz-LvK}h8SkaeTGeO*qeOv$66|>_|y{6 z>_jqIGyH>N*2}flW$sAfa~H?UirmjZ-u*gl@Irlr3saezdd%EN&eiIUV%qC{gPT7mpYQS8V#BD#=lw(!1HrpsRY2^eYWi zW;!h}1k2DW72%q{dX%I(n=4QSppWj#fH*n?0SSDM z+~{k$Q572QYOra0toCH)foH6`oAnORJ7XI2gFw8*lUzJ;E82nE-3-&Yh|5tHg342Y zK`_Ju4$|>mXheRN#%b|AUzELN)z}v?Z3{@G04vC+sD31BZH17SI!M}qVLgbdrBk<3 zE1z_y>M)QQL1iDXeBzWJ-dzIzt5)B9m?sfSJ+1N&cF?}FoY8*n`UQ}v`pK|)xa+rd zEqMNr=lN5q>xCNh)JF&D_(uNlAAI@*O1cz0zyD83My|K?XYb1ve3i=Yqrcyr#dg9h zQ7`=zw>>`#9^>}`p-MIPd3u+EZM%NwgHKzLK`>eOISQA&S-b9BgI^sZ7q-wVdZ^WW z6-)mwAd6>K&%F8eD$$A>PK3G(sBUXMMQSzDWmFsL4ZMiq0t<~qywvROT@C$$BYR?s zxP~&WxxL6c!usEHw>kNlMqy)p`i^zVY50^uhnl2)8hK z7Y(x6=G*(Nq(gU;Mxe$LM6A zeSC?=>!Y3n{-_r1rdg^-#f=pok8zCmoPw87l8?*TWs;9?OIRzpxFoe)-p{_M=0WUZ z53|In%(aZx@LnZ@WvMn_zV| zF@Z44v`a5q&3_wZ|F~FjPC)NxE+Gt$b?M6AZclfPCT#jW2JsrotWUhmyc8hW131?5 z`2_1KSDiLwixhXEUK(PrP~EONA*crRlxC%#(93p%$KASWr(*a}w=_#d-YzCpl*`f9 z=5e}SC#rUEHk2$o44r3{i)hZl-Hi-{Bc%JejhU^5XaQAO@$;i8@?Cjke%fK0%hP}1 zBDC(gb0aGf=wZv-Q<2HFM@VsTDWN)DB*o{~k%ET+^^ZKUY@ctUvxq!wIrq6D1%QdL=xsC?RyKe@o7qgfL#1pA6ttPfS-&MWCNgBcV|BG8B1UbU z#KD;EFa=hhx|+2DBF-iSa3~8;4Y}43oyRPe?I!%x14x&t7w&1L3|Qos)RB5~HRQ>k zL?#zoQ#muW)vMh zGV!)c(Y68i$5^0y)fdq3E^XE)zX08j1bfS-7{yaF3*G&ixpL;aXkj`r!U#@L$?6+wr31s-Bpsf)QcY)E0rb#pf~G$FmK5@~|EqA0eYqwI7RhF$ zw8wGwn*EZg86@ReJ$O)={#b@=T&k&_!45E({8nT7T~6B>WyD-$1TlR)frVCdt!76L z_2=RbFVZhx5&VXz**)7gzSmf2d2W=4EFda$t6Iz%Gwg}atyFc74(g{~?2^*va6vV4 z)jU!ZuCVO762!ZC%kH49BDuo2`eHR~mUT<+7~386TjOXVL;Uopeut#+#9lNN&&z*& z{ci#2!!ieh;3uwjn6b=M4;2mV7hbH%GumrYTDsemM52f{|vNON=AKXjlhW%4tkxAM7MI==v^bZ0pRz|p53mHtuz8yQ=pWhSDAWeSYYJiCHIXi;= zEG(kk^Cn=GZJ8fZy_2yG_(NJ6nzTKbS9ZdR{0E)a3vu1kd+#`u zfwFK*Hpara9nXjL&^fV^%G}rqFUb}Nl`|q}!z2ECl_V`xl6h)vlqTv5cw1c!n7U9C z%p9SLh0{uV<`kH`(R^q&oZM(x8>J0nKb>ZkTX8Ghzk}>bf9H$|$3ZO&i(s7E=xWs| zlKk5C62R@k3Ejfv%kN|RTmx#AWub3u8486+-!;#`Hcb| zQ6B`O-N!>VV(SJyKA%Gv)X!K`@U>E8geUpN=4v3Lk%DZU;b}A!!jeN(1m;L%l2sT> zZ!*3>Pwlo*J%Y^LbJ=Lc(JAXyXW>I=6ip#HTe5RhLp0IXX%~64ZTjYab($C684X3; zcaHo1vB^NJMV4g^e}a@MS|Vf7UIeM49RS$V)bt^N&_)+wPXw0{9vYX`^)uF_l?X}fu3MxWEyk@uvp3a4A*}UeFOJspO|3DjbQMz<* zO?X}*<~5C%OtRfS#c}u`5CiMxb=E;eh6aV0J`&XmiET3tCMC!=eTy2%B5b>!EtdPP z5S1+DlhBFHNWEXJ(9nTqiWOh8^_1UXY<7pi=bG=NnHL&6gB}ijoucX3OzED^hRjT# zby3aYv<7FNCr7`d8rY?5N3AkiqK|E{tw8J!l45TW_gpz(hM?}}%sL*A)_m+A$H3z={bE-tnUrqSfr9Ip zO1G#rEV~UJaIBXEJ>P{@t%Q}J#P-OcT1REg721u(U1rFamG%wJX7G^FZs91ZqjE}J z$Kj`WHzvn^u^i?1)MkNxhl^twl~!3BP8H*k&h_iKh07`xViZ~qtm5tdX;>YnvxX&7ZaB&4j@i_IZ05Ip9o$?bs$UX-_NctF}v<_7j5`t~-oBN$H-% zW{T!?lK=vhZ3ETh1Y!grY<4DNw)(z1zWqTNdKl`N)al1R;gx+jI$w0l-G@>yS39?Q5`g7DaN%~UT&(~>uYXlcJMMbVgm@Spv1fCyBZaR`7kGZOF&cj_#juB{wtETH2qdN$S zG!$b;?_rcTBvKl754r4^Z9^O^5SzUaJ=2aP%CCx`Io7g2xvlFJv>t-2O&Z;WqX@rx zgn9K2L#MSJ0C+_`d`4dQUwO|#D9Kt{M^iI+NbCXh9B-aq0Sz?c&zUYW^*XjPg1Ov~ zImRoOgqQZLDp;Iwqnjd^@D|%90{6h<$SS>ttPq9kVwAIF-fUlFid3NQ5MBqQFCzQx zTC3%kg`ns}>=QDiXxD>Xc*)VNnVi6=6tWFp_v^xwRGF3VjeFZ5%C@;@$8iufZaZ2V zeq90*5~Mn5Gm={gkVNn_2|JfZ2K1Fp_FJ3g!^DUyNKe+uUxxvUL_E3a*MH^P_MYO4tSZ%6y5U4Ps zkb6)rTK7~08CAP<*EnIU132Emklm(jAE8JUa`<~$2g?EhlL3yOxU~}#(47%nA+r=2q zA!>c%7C6C%x{YEQ1GzEAJY_#VF-9F;RAMaJsu}7?Yp!P3d+3_a zTZtB>h;fRZd@8i|q>Y z_9_(8A<8akF)X(kV;{qN+%+oEIhh{AInEUj;RdD7lRhM}!D=dJgtLDc_;K}Nb3=*& z^o5z(=q~dej8%!CM*5Y5SeBprW$JlG(ZgoL^AWe)h;4lAy;5XfcHM9{_4AiQjXsyE zep2s|A9PVJCX}l5X=zPfxSEd(i$>p^l{oG~v!J2G;q_={JUIrnQ|#QNvSozc0ILqh z-&J)uvXa8Cm53UV*1;xZlvsx+4RaVmOKuhe7%YR*z)HgE7kAkmO|)+K4{9{gEDV{b z=H$QNLW8RIuQoS7%$<$Szl8Nd))6*NlNsG-@e=U^=a=j}_mV<_<9Vwj=?D7$11r4{ zpS~=2pzO%JK5d`n>71%&017CT+$ ztfsEYM-xjC##Ig4QelQcsZc??;z>7b&WPT8g0H!}T#7Mghh9T+mlmY+G33A1~6Rq%rce5H&U>tTHX zyisTAvbr(S;b(%zk=6S#ls-cqA6ZIn9{A6~(U20yfPKy-UDD&)Uh9lY7MCV3CuFU) zsQ`)S5;3ax>;ca90jPUUbE^#A|uVEt#=O*9pjoV_xsi+81s_ zXt%nwYDB)4&MY+tI5h?XO#~z#2w{}Ct*s0f>xo#KOBWq!Fj?ClOBHoD!r^yVedk;4?vrF=NR@!E4WUv&~*NlY(eNeZ_a5MLHK5>8QlT_X5 zx~MD-HC^lbY3s6dYR@TpWVNwUrNA3Izu;P3F^4Gd_WZN@thIqK)vw6Q^YxfxSC#X^ZQc4&w|tOQ#njN54n(M4RG;y29Nvu9 zC^?y0GtA~N|JG01FW2-*2(K@azaGI?bK3XLUFzc zoyfQqmN%TBk5#__{?o4mtfCezt>>1w*k6Aq4BNy*gEaG}- zBMA*O=4s1#>>&iCH=JgZnbhp{Li=}D4JnTp(`)h@&~j4dxksuQw7S(w5%n^htn4hr zduQ1R$tXF^1b8OuZjbB3OBpEEyMjWGP4Qqw0Aqu3kJxIXX(DV#LrvoyxX z4+;$SM>H0xVAh`Az9fFiRfq+;HWmnbN{L?|)LSQ~+#9^yHi?Z(O43s9(p^W}I9C)= zRw^+S8)umkyIo?BRLuEzfR4GV_oVw7?PAu}6KM{p2pDwdkZqf9Egja}j2E>D=(9hl zbL|~q1H^eP_QrV`3zv8qM<|$r+MJ8ozRG>Z#MemZVlknyXO6);V&*2rpvzaD>OAVy zG&J~Lr&Z%@f(kB{>RUmRqA^A16Kg*8Av4WN@!C`jDcziG!p-gA8BzOAKxpgw`Ss;9 z!PWT@MinfJs@kc~;TPcSZ{Y=!VUk&U^V1!)yP99Gy*qyaEUW#!UG3rYKOovir=`gY zeLOhjTI_Xpu2ntLq2U43eUuz5!49exp37h5Mj>Jq#)HeU)wflN9Rv3WMMcQAW;&f; z65=b@Wm??JMu|xbsX=Mt&sSXiqG6JIWyKeLHGvJM`5>|i1C0>M{Q;|7dJ|Ev7(iuT zIO!ZiP>l+}rb%t?%Mqf$1am=2wi_Izqc1@42ntfBgP}HzkP&@6z z0yf~_fL;2>isSruu9~j}A2e!-Gg9eNM_~`(p3%9*vQ4g2xXW+dwRF5V!a?rl=xO6A zroRV`+e!5oIpuI@tBWWsQge%R_zOBngF9|VG_XDo3{;d^ zPy47p9{EP1qRV(XWb|w!_1jn>yJr@5Xj&>g7Yc6E58^K-jZ$jru1UcP+YYIq)$8wCP^LXnA_jj2!88ZpH3= zmpa7RxYwYc!oFiJO3+zhW3 zFRH_i&p)^O*+(|L;~7NHP7xo4nMPPtJhRcP9iFQJIY{}MZT2%#K`in zLq*a966ssQrMa;jW)8}z5*+P;)(YeqL$5a%85{1J!f{K(R=V8qE4!o_F+)g-*%SB> zNgczKs1VvT%mIxPi8Z$)_{re0obVf0B#-#8&$NmCcu`;n&^1Jt_p_z^y7 zZFj{Ils11I{zk>E!**{F(7!|vpVG5g`Qw{Nusy2wsi^TFyN1`kd;910LCtgy4`w13 zz0?n3xpC}1QW)7uJc$`Vy;|t??JoBLn;x2;Zv^9uvdN{>+;e)C4Rx+T_^H9p0b2?d zF|c_g)}kASV-@e~;Q%#64WwDa<7|18RV#hab}*SP@8u8pGiE_?4islVaqjot%ss$P{OkPrZ+RyXK zwq>^}_jA^ZR)MwS$`WQXwVazqCvdrQY3yLu+aR`)S}j}KDA>z573JY7ru>S&&$_hNXa@snBUR=`~Rb&GwSe8=F|U60cN3Oh$D+UGu~nQ2Bu@`sOdnwnK5yt1$^6vYjL)9l3` z#@OT#1b|i1l?RHMm`Sx-ymHvs{RWHU;n@h~G%iu68xfQni+lYJNVF^(kAunIA=>eL zjoHBiW?t+Khba{M!mn6p$YJ>aAwmPU@jty5bKbb6{{r;I|IC=PRLH?{%a|*Z%^`Ce zn=6yfp>z8(OD$z7Whn`YqL#Xf%{kV*tM1LG4ioMiLf`X2&ynzF4w#o?eJ7vj}?CEJ(Wfp%#|SPK3_|2|7}9idD!_mrsVDti|`D3YM@P@f|TQc@K?-v zwAMcF)T*@nG7HCf)`Bg}0rfK`u4|elEPQNz?QbqH<^(F8dD1Mei*a<>i zKD7w{4c$A0OlwCR&+Y`}H$!>w1=Zy>d&#a59t#+FOSoo~>sAn_m^0gkAjjx9o?#cr*88vV4k9PsSi1K=O%_3! zg_dS#xF0!5aT&@VuysU1ZXfrR!~*aePbg!)8*9Bu+raM+Ew$>|FRiqW0!Lpny2AT3 z{>M%4?@5`z=4O6_UjSmzl|fENw?rEX%`{de_a3}7X*IX>1MOvXPgSDAl@F|R5%wn8 zxAiH?GaEWo+jlZm##eOIha2v8!bilbJr1L6nI|K?{WVy1^w-6nX3Sewe#G@SRq$;> zWtdGe%169WkvxSRvPW)4i~WYMXJDfCwlL!|1$R!rt9voJeB%lX94<_>?<_-xnx#9# z(VmGXv67ZUhk>=;Ln5%}(v2d7Mi;WX{IP5ylR~%L%Hb=G6=f!J7t);}MIcgt4Rb_| zw>Vc)q;_)7P-F8P;kSN{86s?Env}?(uB&+M*5p)1^SDkUD5hKkvuT<($g6Lr?&im- z-0fWrwD~h$of}+snhY5j8TG-k*MW&8Z$^S69ml$z(>l_%s)c(Ts_#pW`;JdRT*4~-c1=Mwr z7|_+2b~pBI5{IMYyAWD2(NIT?jZ$XZ=q*-ErG1~RHe=8aW~e3&NNGD;p)@u8LY%rind%s}B~B%x2+Zqh+qtC(Nd;d7 z6&$R4Q;RX^RkLkW#bbT)-+|kU*9Ks%)&_H~?BtBRw~&$iw(Om_X~%Kk&9qc)kTGCC zx?F(eXU8F3R>&oK3SXNH5-h-I8^UFc;x&+0`6&*12G`VuubC~7vpYoM+8Rp2dp)*69N!$deE zBT<_q0_OL8p`xO3c7;$TQwmBtsv4uvV+rN1M3U#Yy#h$Sp8GG7BPpw=Cf(ffW? z_GH6DOx}8>NOX)zw?ublG^j=i3oCN3*^`C6Ao3k|0kvlA(R+4NfPVM-rw^y^H-diw za0blay|zGRa1azmyI@{egKR?FLvicaTzcg9e`bt&vlss#jHT@VXJhj3C#lg`C?`Q= zJ7OG)80c&VBXP`s0eb)Tx{V?O2Js6(@PQop1^5f_uknQ>ApAwJ1SEs{{?1INUiUNS zx7+RiazFoL=Jj81^8Y~ezj+{esSejbZ@el;4-`}-@z4u~2#e$Kzev}JN)T~~IS(J# zT6xzeV3=JCZL!{~qfP_i9VoY6qr0)p8&nA;+^uPuT!yFbiJyMp?5*VrZ{o$huJryR zt)ARmyBPZ<9N|e!x_R@Y#YfXPt9F#J!s{)_8hF;NfpP0thg)p>@!bwFor)nJFuKIk zO}u%7((`o`%5Z29&F;6>H;mDUc;=Nkmg7m~1@9Z<2}8>|GM~$j1RFUk zWs=@l(GlKNbxDnOt|D#3CC;Q(?h%}T;Sayxx7n!{U(V8n??$;)VKsjEAv%|4BB8`Y z_l^f*khMI~flbm`s9m1i*-46CN+#4$p@Nzu&x zuoIrgM5%o&0ycYU3koa|p(#62T9v**{*U}EKCmpa0YU$#0|Qoyv)&Q^w{zv#?o!D) ztWEVC{98f?Z{r)&v_9Q*z7uJI3ff$Yf#KviU_mOP(5u~dLhQ__>;E4Z4NiY;J<}$m0p9OR% zv;icL(2xPrVEX_BF$B5`X<`ykK`=O+KgC};;hzBvIsgE`Fr-dX2-uYUk}fx_kW{9M z4OQXLWIChX49W+_)CeX|qdbPdQVZ!)RLM#M_gCR<5X6KZKq>(jQvjeN5vWQNsKSEW zCK80ra=&Jg9inIe&^$#73Wsrnk48jTzY;;A9JC^72FElYG3Dvt>(tIY6_dQu0f4rU z-(Ug(1p<;Hf}}o>1Rw(p`1^QtphR${?Cc@p-?;z*0Fpll0A}|$0tpa>BoRem0id7+ zLIQ;UN(8_KfH09S_2dVCQ~riL4js&5!OQ$JFZUg`{lYq!zQ9w#qfP2v6 z?+b*hA%K%jpD`hF`i}*x0>u8w*_y9usFfzTEAJHm^)&yQ6PW+6djJ64pCI z0VD~?{MR9j6~Bp~hJhpj1CR&;0VvoO0fkr-l)d@E3sp z@_`vV;Fap|rMP|8-_ztqS47~Q3l77F?v%h7P!|@P0IaR?nnZt>j7#6^Y46_Q*!l%{ zxwXgmO#=i*ppXH%gAh=_;s7KO05M1s@(7dwKxITPN-qEh43cC&06d*L(m#p?lCD3DQx(f@XNnX=I90vpomR#(A-T4!KUm%J!37H5@P#{IU97(kB_$qiEKW<-4*)__c7LP0?Nb=F|0C?qVb zDx|{7BD2OO&L1uV`z}Bl-3>dSfe;W3r28#|@(TdX5<&`8z*9{wp_NrV4lp9RN%bu6 zwr9d9p{FV6kVw^$z^v8~hGHeWtfQaVmD$gg-jDmUmEO+TSn&LV@ofLHg!*X!~zl4QqWU_nbgg{jN^|M;^1?=@&p=&)tXnrn_vq z7+>vOUYShW{O!%@^1fR!ab;cc=KFpwicSAtXMCyGLY*6RH_b;18$yMWE^5LiY6d!J#t$3D$)*`|Py?m$&6F@< z!|Bt?{$8Bg=MKYO`rp1K1-DE`6`e0mYlqAptw`1^TF@^ zfnq12yHSLE`M-qMihp+yZo?2#*e{?rsMS1~m6eq>_~Uj? zzJp4X5EK!}L>X{V4vczuVbx>ZkCe$v9r!e9jXq>#WET>^JjfN7~KVl#M-Z6~2(JFk#a;|pKoyVhE%Pz1B z@7vZVf-KmU)(5U%MykBw5|omf(_PZ!hNY1J=0}K&i=v{UxE4ma7{EuV?#6z5Z3t&s zeZ$O!(9m3{e`y$56}IeUt2B#Fc$%Fxvh1a1g^L{2QQvrzPRDICSE$~09?^T8>0sxZ zrRJ-K0emDRuWug|>tT4Pjg^kNWQH$?RWw^WT+(_O9X^LN5o3 znVFfquAgg*q;R}?iK~7AMC$Us{2=3alj9ib+e#K&!~I4zb}n`<&E-t@ zV&#c5^Y6|^UkcI3FHAH_`voY7B6rC;*L{z7dsr7pm4?x=dHv&M(_?MFblL06(8t{u zzcbplCn~0SBb#Q$%45~!Lf1^&@h^F`@0FH?s890};d8S%UsgJp=YVB_JjGl-^cbL? z*9RSGlIFIQsyjbM&*BkXcxuWTs-{h71l;k(yl1jelEezJc5hWqM#53Pn$+qQ>$a0Vx`22nI6v=Wx1rpc;1vZ~PqX7L6dwz@i0`am0m{j6ccGwO#&@*An+ zn3$OOS>i%AHa1o&#UWcG3ejAJ_BUjuBPd_4co4&qi3tmr-3{m)jij{gF%i!jv=R%G z?J>&BU3)>^cg3t!U<<3&H5QGetw0sM2AL0_5DBWSRPsLush=nB2CK`!$X}&idR{L= zAzF^i`#cEEKkfSqAol5xC6C{xJfOg#K`*wyEqR~|eap|5?!v-D?P-67jXqAycP2 zNofb$ylYXp}d8O&TQ>M9}ok2K`G5Z zZW)gq-eVL^!Jya@K|nz*)}k2h!{!iEPJ#-1|Bo_#&m&HjZ+k?*#4I%;dEIO<6M$y-Vu zY$gUWUhaLEoe!=<(5A^2E!`=kQXIk|1}L5PI=<{!hN;DoC8uF+qU2G{G!P*y@>VU9a8BPi6y&Ug3My+y5Deo^)Fq`KQw%9G_%5Pj&8I7dPOAE`(Mkg!m*?FQ1ZXH@?wcJV0k&sPz&wxw^Z_ zYI!AeyXysQB|4tMA%4)t*<3WZu-S`d%mn5aeqswns~)PRef_i&X^hDe;B7Yf?o5~f zk(7Xn)P6WXH;_c7MMW=4oW%r|1CqpJh-#SMF+{w?PV=1CtA){yj0_A6Z=3O8?j>5_ zC1EZQM-3NswrB*920r^UBr;|y5EGSMKd5QP8&rm<-_5>!-gJ2SQ`trMk32k3 z)zm-oz+bMnjW&H{o{xq;b>Dmb$U_2^I6oKU1l0s0EdR(Odb!@&+VqusnvN9e7TO09 z(VwORgdi}H*u;k2SYhwjj>)q&9pDN{C|cQGYk_F{R!eHq8UH^qNsgp__6k2_D` zec?UP-QgZxZD6{48MS+k+2NToQC&Upy1#qQbk)Xm)x^}`9`(7ySzq(O!QjB9Lr0qd z8>(*!sa|GpnO88qVdo*zou>o#H08!kACee@Q}nSXx|e2j$&e_1+@FW(O&uV*hLV5_ zxcu#nF&qCJr~<@RN>#MSP@kbUd{nwPVKZ zyjD(s=`j=_j0Q^?9dKz|B(5_4uJ3up)GCuxqy)8_r?s1dS<;Zg3y~X9)*#uv5@Sdb zY%n3}l<(0G8-?}&ScyL!^0Sx)N1f&@AET%Ira_DZ@hVocqfchgJ4A`{7l6Bh##pug zR=VGgQ(W25_i9qXuNK0ix!=?pGLxw}?QSSOc%`)8Q7W zbNdEWJ;9oZXZ}Xa^F?L!K(1#1cEYtK6r>*C05O9j+;Dgh^#VktF*~WaRfhT*ICbFs z?7F1gP*21&7=p;yx_~f^f%>G5((FgqFrhtv+L0LNBO>P`-g~!^7_X`Hz3L7Q&(F9` z-`V|FE$ZqgiOn+w*NE@G`JaM5r*5&ft+SS`vs-SmA2b)6q5KP0zO+A@`ugMLg0I{|^_!-|==ZtkH|Ye+HM^iO&w9 zmEX2{Lc>VisV8EOzp60RW!V^ zBHRLY5Exk!&vh$Q5MT}Z1Vl`XG>4rCLtLc8;+Sq*_EEWM&}-`!h;qT{an*(4S~{4( z%ETB@WHEaIy7)ugmntM#((V}k$W+9Ngrh<4H_7y$@*MD_`9rxO6*Qi9rvlo*1IIeu zo!=}%y2W$Qtj~Dd1lS9V85F(iC@Q8VF1PCr@X6jqjx47X_ZA*WB*4~J#=gIJ|3ygH z4RL8k8A;jFXjWaNX>8*j$M$>9PcUB3AzhYZ)?4aY3bpHLC{uvFo&ptZ6(=5}3S=CZ zy1pDa6o6)^*awyGf9psb`q~(e#?PO`Z-=TMwXSkm_lQ_Wh+J#kFHS(WFcEtfS1D!~ zv)Ps%28kQ&3qwwB!nW(v_E8pvVaDJ{xYFks*KjyLVdnfHnFdcr!K8;SFCxq^X_{MQ z!z>>Y@s3I;`men(%g83Pfx2`&8(U|OtkjXYHf)gE;rH>?7s(J(f@qG^UwF>_rin#) zxLMEq;Z=^D_ZXQL*T(f%KnC(ioUyNn^+o0|`SXsQIB2xM;ydn+x~?v~Xt~{l%1;A z9}fs!u*?G+(FUeK$LhVA6ghVy?h&7etysJfOqoEa&MJsiZ^-CF+9O(f)KrdX&v#?VfjHtXa%*0Le*O#n2w#AJa1k2ky}eZgUa2J?-$JTXJM>f|f5 zh8dXovhz*uZKW5kIBE@U3M(W#H23KH8`}8SkSLBGJKqQw(e@7zUtF zA=w+{GiXN88BoK3h}C+ZlyA7Lwz3vFX1KSp!}W#Fb@M4#FvNgof{opHAC>wuYj8Ft zf(S-gXz0qjI9pVM6FN`j(-=+=|k>>`?k~b;YQ~6}%jr@DTVkppq%w)J{e(kGK z3zM6HO2z|?g#+G`%GTy$7L<&c+Ytd!jw0J4_Bn4~BruxfeM}|OMi?#+dA|o`1uC#& zx{v1=n2?a+`z63i+XnnZZEi;jGdR=sP<&ttdrK7}t~jV=zf9+RhStj_+~s}}h$goi zrF~B70V5BPWn;5*q9cJXHT%vD16y1VSB+gy3cD!HYm>BT9*+fu898kk7!Zb`d0NWJYH6Wz-$xRF`23SAbMglnY6_go!e}ca#X{*k zP9T&Rye@#54H8MKKquPiC9Yh8XApnOylr(y+-C7;RYkr`QDVf(BUhDB-ZH9J<&8?N z%f(5Umv&n>c75PJ4oRiXu<9VvI7>GwY5X~Sf6Bl<^B1Q&=028$~`KsF7v8(**3CL z{Ut)AX#8xCK-#adG^ZsZgLP2kK!HG>9-aZ8er>~<84(QK3Zk|s!r~kP1#YGRci{r< zRv}^V&3G44MSsTT+xTyX#&5v}rue86+!YQ%PJNwR1O`nh#8J^1fwo3;Z?DZG0Pt7TCPBOqA|4BwEXmcOGlVv|&a9}J!Ljr+&0#O%7*OhD z0XldSP7v_m9InK;U8oLK7pm2-!lvMy`r{bQMf1PIJet&?n7freM+nq^miB@WhJj`Z znjCfRi%$;=84)&^zMVfw{s4p|Bn4O`EbBdT{bo1kk?hkPe;9eMDck|9TE}zGhbfM? zSk$mbyX_|6sP*0;d#AgI2>>&@z2{FQAc-Kz&nzUr>xGn81v81lMPvQ6vqZttUfbgK z)j#gHXq?KJCvg5P8eLPZc26=BZAV`uefNmDN2XY>WBzYKSbB}!P-jOF_98!(I`hLQ zT@=#{X-kGe6;%W%lmtP+!kr=COTAw2=hGb5_`&Tdo`yV`7nZyC-}bUu9RF|~20($l z>NMcMTKGzVo4qyuE4C!X4qEaB)Y{Gl6Z~E5y3I!n$3!adU~$0LI%sn9KS}v~eGZo{ zCiTkiXs-7tE@I!2I52UTGTl}1BWXw_b#}6^dwuT#zK?@(=GaR*MtujjyZZ=7xP93K zp9LJ{x5|djA9#>g>emQ3b{>Us(H%Zamf#y+j-p=4@eX7^eR?C&M9?N0y`q(!|5jt( z4*Fi0jqJ2B+GU}hhJT#@+XU!$Ru7(NCP*_3btJc~($^k%Mvvo}Kx&Hq>jJrP@_LWZ zd)8@Ngx`2N=-@oRSI`duB^j~Hp`QOfwhsF^(4=n zICW0arnyDbMlM8d@wzf{5%L=7D@>1M-rb73++IjZ<>L`+ z255EjmEt+WPFD@$P9(xeBHYnimUnZWAtUf06?M|=xct!`q@#q?b%L?_Mr(xo6lNG zq1b(c8f(QCwqxj10`B!i7?_essWRUSS)TQkA~O&V{_era0}_R+wq&VXw-umI0`bS; zv_E4(3gQEVkpj$YtK_jD8L3Iatu%-4t)60CZe^jWCEyAtKpChu4dmcdzM&8&>Sp}($m656~KE?L# z;#m{b6Pd(VD748ADx&r&{ zd4;4e_(5mzf>4MBE)GZWW#S1}d6zCd=_Ji$N5`AE{-C*)#u~f8NQ+?fXTI<|M@WQ1 z>Z~p;#9NLyrB)e9iF`VaB2I#`0IG57j|ztZjgpZaVnXud@QZ2*Xq(v#Kr{HaB2Z*! zirg0oJyPCo(a*caF-g|cNwoX>d4k2k-m0{H{DbsDg8fL~C=#wqg?GXq~j?nVdLuqHFjt3yG+xbX@bv z&WKtd8NR_;xevk$npxQmIsS@v=1h1x%~PhWu17e!Xl0MNFiE%n$8+F4ufpniK7MyxN}Fw8t5DjQ zkgt%)d)cg_sFq3Rz(Hq~ukRu?yfwUg11=ZzWaR?rd3YOF5-qFf50uvNM~1{R_y$#7 zAfYlQO!BfI@PTcH2qq#_0r`c(_jwDrWphRU4UV zSX%u~)TDN`$sz4+@K#7{MY{Lc^|dvm%{foeEyAt>2`{ z&?2YY#l9_p;Y3QXN5=&fmUHlzg4`q}g6H!>fuuWX6zsr6O=#F4>T!z3J4fuhoHS@A z0Q({JQ+z6szT3U&LSY>ZmM}qt5nL;*MzI{6v`HqB9SH=4v8Z??7C7X0gIdMM9MWxoC8A2_YS*D-GiO`#Qze;*9%+?iknLBl4_sc_bufduzh{mztv< zP~Xh6$|r8|Z6~6@5Vz(8e2JxBqP*nsC!Owh`ew?~gI|6^0yI#(T-u|O9j3>Fun{7e0i$3n!*vvd1;E==6!D>_`XoYZUF{yk0~8yeiT_9 zCof=)-O*62Y9{Z9s0X>&U+i>`l`9;&X`S!B2gmO1g05n1`mOK>YW%j279IKBqIF2= zq;cpINIq4zU%-(=raLqD$ARl@!l!p5A!!jq2 zC_zYAe~@y3*m4N2m=mQauE!4PfdFZ;)vnqO?41T7gP~>J%@c!*km5f4MC!qxDL3~@ z>Wzw<@?*{IjFP-R2Y{bewE$+;b zzO(=@K5|pJn)H~~iTlKV0+m&#)e|GEORtgsr`@Txj4A6A^Ia_gT1Dm@gj#uM9uK(AM zJOL;F4ObTO75&U+j5?~X!OT@lx8DxNh=Q^^^gG;%_!QE){~@ zd;L9j`qc5uAr?Kx+d%N)X9siW6d8I56MB5Vbf4`C%z0v8WnI3n!fCV1t6*EMi%`%f zgtjvJ#yZLE<35(s_yQ9!aIlV-J9hF{n4!gBP|ni{yA(CEYFhR#Luz*ycuLypw%oZ* zhY@kDTrJ=+dAhaQjCB1Jg~Pi@;>Cw7J1NjK^%_GS7aqiW!ZfAq$_L*oP@A5+*;pND$O;^Ke9I*RHbQ3g*`l zOswMT|#h0JBd`iTg1_0ZEgiWVX@F%&>YQ$py+ z@YvbD-F>i{3ZtBAOMZ@yXXw!sLHzr{oki6=$>ae&6Jr-`meh*>HDy9b8rC<-bu;-@nVI?t# zED2ICc&d>^%UK6F=c4+KTn?nW;iJj`%M$u zaN-iBk+javp&SeA>*m*PBV%^JM=g7EKL~aU8Tm%r0(LdzJuUwLyr18~n(eQZr!7;5 zx)dZJh2wW9G)3%-n~McR20{D?4=!7wKc&)K4_v^~9IAk0STWapS|09#d| zis*ld6=oWicRi0_nLb{e)KaexUPjQR9-009?&zfesDFKX|pLCucj^EyKr{OHC> zcXpwS1xBDgno1;Kc;9`!gh^1d|7we@55fn4QJD3Sb>@Tzi$|7VgUiUvUY><*NjlE0 zAegCzRDg6@lg8Kx&@%BaX^J-AV3wG}%f1xE>k2foS);nyQ`CNERWZ_C@q*vz&kwHL z+>BAdLTR53M_kBZmYV!*`hq}k9^;xG;1Um&k;z{Ww^&%o#(*^XXE1j#Fm>C$6YSp$ zp;Iu!0V)B={pvM$a3_j(adoss95*D}eM52gGCIYZ~DZ)mfoY_fPw1V&LdmZ|IY=%>v`*pW6ivNHe@0xoNC5uQ7&* zV@5!}ZWB#C+Tcf!pmRmM8!?gQz$!DR;<>Go11vNHISHWlbs{OUHb%pNM{J1xWZ?hC zb2~X4Ujdwy!QKvj=Kd5dX|QG-4oLz-h`8Bmp6dr=BPJYgQVRf9-%E2kc95UUkrTAh1} zRzDdqFa{VQ_*OThtkW$plVu0!v}<>;kuz8y6}HfN2hfnf7k>a(oE*K2ZK;8F2LoCx zs7w`)aAV#$132pe>jQ9hw3l^hi&t%hd%-@8OgegMPN#^b0WAR6GJs}h`ig#8ERqZF zyDiX3GF2f_mSapb)D*M1;(Dg9HHjS=~Gsif>ACETu@|VEfsOG z-5!jtuDq~2ubb1iAI4sP>O7F|hYF$cx7XiMHb-E{($jhEid_`kY6i1CA}Pk2iA1TO z|7Qh50*ZvEE~Wy>w9idUaGv>+{lJGnm|Mj6u!wUo28lJaFi7m7SN`m=z>Ywx$5=dH z^?$Gm*>hBh9TSjXpaaH|3!|G$9)x3aHRhWe9A0ux=_wd|!HjA9W?wGk5wqcaZ)g3D zM*BUh8h9WMDZJWYzGeKq_fF&dZgSw7s&|TTh19ib4mBO@ZlSa!ujq%?OP`8Y?xcOC z{57Tdyw9%*ZV7(|@Y{~3Lmf|0g#JYxMY~=hr`$;M`FDo@*f#MK7J>?lHq=_*6iVRm z=|adOV)gGY{{h6`v*?=yiZ$=sB2q&(8hUl{ZMH$yPx*}U!WGPBB6)>c#DJ@6X$0-# zcv7;HuIpDbn*{>V?!Wv~<>lKCgOSE}nz{GOqf4!OJpy~i_3%>8R5#6$h4IKdnhu41 z#Z*_BZEc=CcH}<72OB(;+&6u^7-CY^W+_fTA}a2hPRrlpC+=v+z&tmJ;M7Hh2oQ}F z!d1S-8+wWHiJP$&CpPrEA#px=H7Pl^zRWazqnq~cl(oB7n_p9Z0997J$eBEcae>3! ztF!M)!4Z#Si(xZq4pRb0R9_OmgZ~ylQ3c}y2f0^?s2b&ubc)$$G90|VQ-*Hr_56M& zQGDh5Z0eJa=X#eXt+KUO2wu#hgTBSw-+W)Mgl8wp&vem=8V*y*@0=#jS0+!-h^ zSCn||P%cGvJfjqd`|oeG*N+I4{(X@$fB1a1AI@?N)cesA~FC;U2>gUJOT!^T~hN~WdZ#VCv zNwIA!50wDJX)0Tg`~jHKRe@+lNtmbj&hbRLt9EZjBEMarv>(xsjK>*^6YdL$pWn9V zyozu1sFCD%^n#~p&Ya1j@Hj?#bkVG;cLIt^HdW;5PhW+rx4GmA>+&fQ2KEjgV{>Oh znM%fyo=gqyhCg~F+nfb#1)%`yR1+ulHSe}`DpDVNAMw|1H2N;uG|Whurj9s>Hj9!}LAdO2hc(od(lM1J_Ke_j{Zn zF0$3@%K=XX3YgHAfT4J{Y_+zO14-0nUSXIt6IZ8m^Y$sEB{Yz^u?|KScMyDVU`3@M z(FC?1FyD%s{+){6W`Qg0GFHv!H^x&CFShq|a~Nj|t5ci18iWYpdZP&Atcvfx&XLAVRAQTOUINv zggo*Ed|B~iI-^7T%8##m(5()Kh%`VUjp9pxai5S!p6E2~zxZ0qrD%)l%+_~&{LGJfwLJGe@_b#Wzb5De+5DNNoP+SgV0{f!JL- zBK-}zC?`$Z5j13On57;$@F6by{3q|}ft8wkRrPFDzj6DoU9| z0xMQ}Z;;1|GB$!N=#;~97VWoUp}@Qi&y`4^gQh!Qaa#&9BcOM*qVVK3=Q%INe(=FF z&AuMO_;^d7m^moKmD@=FvdkgR>y(`k`3K-{fijHwo-8iRs1}Xj4ju{dniQ+xC$=v< zqv!jb|6n{Yui-H%Nr9$QE+}G9l|8VhUt>r%OB>_Rz+gn-ceAgNE5Uh^p*A)Eq$USk zDVQs*sgdOde5^B==7Tu9lU9x3*-xq^^J*_*U%9c4{Up_l>n>S^5#ONpOshan9fDpS z7JstG5vrQk&zL#9#tU2yg0afNCPrrO2X~h>K53wvrEe-<-a6l_NGFUUlat*NB*xUq#|-|8R=dni7U(a%G5KgA6jU4;!xm6IAzOR z6oR&{9ZLl}*Y~^@?s*z46d^iTVEc9aJbj(56UEePQmsM%kj$ORE{Vz8>d9+#trE7r zW9LQh$L^0|`U3s{_baR8@rZSAFBDsc?iHch;9>pc-QC{v3dWWK)}D`mk0q`@EoU|( zG~g?gLT|OWi%cY215jxk57Q^vvi>Wi7(YYATA$dxVCS37W8wh$?0~)6ND?Apl7i#N z$X0Xm7fE=$CUIEv;e>Sno)5v&FXD9?7rnjRG>ybPT^FC}tcZfPN(m%l*vwlu`p?lj z3&lA0J63h2p8j18lyKS!-#g9o$e;`-;|FS?h)ah0CF zGT?QYg*Qoduq-Y@j&;4oy^_~K20&9qiS|U5MLp5#XgWKKrb3sVzqzuOF~_7#Ub2QHwtcgR(&Tso|AzPcv(1PK zH_A#`3wa{rGY$Adl@dP^)%bc5ct=!N_L$hXvUKK2&TKG`6{E#K6TD{={Zh|{VI8|= z$_qU57E|=A9L9E`00oVV@U_Tmb>Ukd&Txt-!oc!8u%iK59@2HPCt~{7`_BvX$1?d> zWxF*?G4M*$E-y=yvF<0=u|57%u_8&i$elcsMs`qJ(;(5dWUWY*wnm zViK=@_4QuiEQapwlT21$9CG`5-6W=slj#v^=Xe-xHo;_#3L~2%8X--1tC-&`Gg3ig zB+T%G4UvL+jqb-+f$^lieSDgR4le=k&!={uflaNWF83?!ni=$HcE2P=3sNFX0rg&+4OvL?nGz?2DXB(vyBWSF`V zFuROhH@<3Ia5>^k%%?DK_w)2oEepIDi~)kxuFx$!9iD->(4$!kbSW=*wAh)OkF1x) ztZIS!YqA!i$|Kn;j=K)(HNY`wXLtk;Pebn6<|X(ksXEyzlT@335OsL@_}hY6HXYY; zw4dW^StJ}2obNJDGOc9FtQzR_Arm}P^5-tCw#edMx}09rPrCZW+%vVPo%-2HASE3;Qq59q-V7joYgStNg0HN)~w! z4|>jbfzkV=c&@mkUofY45~3Y0quA$;Oi&=R9|3{$7xHz5_Y#KqvDovscv^=@p{YdA zHuPl7W&zT0g7u4jltFuMwD7~*pATXf0|(sevScXZoLgxRf$ZdscOJ9P#)TXM?zRdV%)uqp~QCu1IPm?c7w7V}plurj#70B1Eu3ggIa|H!2@pb&nCnV1vy z@nenmsfbNZ&wfQ=kJ2rkb@+kVfn04?{jejZ9HlXEsBzLWpLl99TV+YYz%R>uz1`lm z#6|b*%<$Q-wKoYS!xs;oAT9}~$uycpvAk_~2eB6g>j##!)LU)rS3nQWM2v13x7?o1 zpJ$@(yTdW~wD%|6hAG3{rbf0YWsWg#SxcMmMbm44AwLEDmvc6*-*QSaU*ZN&8IB0bsgzwdY1n5{udqZO0*vLX7Z}y6sYx*D+AF^yc({b(W#X$A^aq!1 z5T@)k5;-)CFW&;&mI4MaWfCChjQETZ7zpqPerv3-C%$_R>_#QoEy6Pa3I2JBET2;! zBO*&!$FdvAZ*AZEpp;Q`jJNX=MhyjN90!Jdps|hZAERSSFIE+zj3ZbV|CaT`l$8i$ z`NdJMyzM(m;?D)f)AXGg?M23Zgi2*f)9oJX97j(Gnx#8Zj82P7afiOV#jeHVJP+JS zDuH?9tJEWn2$Y)6udPGXlZ&+x%#UPCzJKag$ z3Tp1B&-!*`VHi64!JUR4MgTd+q%sb9Hv%I7E2U2VP1g+7h5l=G0UWsjimTEN;l6SC zO(mDuins8kzVnK~_0ixTdaq*vwxyrSg%xz|j4uo{;By_JI4gtK$-I)r{l&_szl!@r zTTTTp`3yPzQ&OQmR*_F;$r$fSFbccazG^xxKE`_4zE52dCwPHx2aKP7Sc&RhN={#U z>w*d_LGFP}dMLZlSs5uQAw0cEr?du>8!bLckTl0&-=s_@%KBSoDh5qPDjAWOfZEt6 zLKwO&q4Cjf9FWIF2Gpm(#a?|8rBtD-O^(BQSgsPjNF4xnWq#o-nDlm5iA9H28#ID= zKn}jxP4@f|OR1tGTUgbt&Q*u+!NqyGee~#Gd>Z+E7BxDH5rcCVWIM8Wtmo_t{e>tb zHHR^_fq7ISa`YOgHlSO3z-@OVU3C(VjY$Ghvmvo`*d;tgd;W12jtG|^Y1;d1jW-N> z;_$ae1fn1MXYkA5w8fx}M~;930RcI03`to0F=PNjLSc|vxX%Sm^7_1Eghg21P_lfs z`cgr>c^?UWahIm%r?L~)M`$3cuvpBfMW3RdB+hi`G8i!0+jq(%8TgUU3|7MtD>A;S zKI_zk+(LPPSS!+M3a5Dv&DV$(BbC*~S7qrmkB57w$4$h!<2tZ~8mk%TSjEL}aChRF z^KMdJtX7~e#|dcoOugJEH?cYi!y+mA1K|4)nda(YW*Su899b*#;VDj$VHn0oeWT+6?SyFh;JbBD*)XA73ylT zrIYw>tSkj7NBi-Uggj@*Mftcs`I2En!I3Z>g@Wh;cA2+!TxUdr7xS%qFA=c{c8MT# zqk_-l@f1ABT~ZHP>arkrkm;%=Uv1$oxtVk26Nv~c&rBlPPMPJx9ZlacI>5QSE8fYF zw6eRM0pXyqM{@dzEK3PE29=-54ui=SAtqs&(?!}HhT>TbT0w&a(>ecuVEL0vu1^an;e8*Vu6E3yQSXhR7Id#$jW+puKk$xJi^?lpug}5 zB@>>f^Jk@Iy7%S6Ix_MqSJ!V2pr9md`Pyaue6+qs|1sw+3%)`0FEz2(uJ~qWNt&!l zbr=f@B2_Y~x-_cMk?c-`xx#&N>?olYAbN~GlgFMHgo ze;SrkxKiXV)kkl4%6S@BmEZOk$_Ivb z8Yso0J!Ja52HJ0nBAu<@G{a;gU7{W>IgWSPeg1u@%TMk-ni8jBi_yg6Z5hSE?qP_$Y%@!Jup@3RlAS0Rp0@L7OQw})RfezfJZ zDL&qQN1X}>1m*CoXF4L6Y^$Ma>~SKQHgr}PkO&xc7q%5ysx;GebaX3m>Xs+-n-J~y zz%Ahn#w@R;1AlU2-coW*1d4uK*4LMd^`{QoSe9F-gG)3rB}ZSBr4y9-kkEK>zoeMIW0%;d7*u9rjhYod$l^>!B`R|OCDo8S}q;F z7bnUb{dJF?kPMFntk+4BH}8jwTM+wTA4k-LWF0oH@mmvC! zf;>6$mF(jRtBhzln~$$-8!!VJ>CCZlbn{iRBIjMlbIo=qOMql1PwUkU(dQiBR-9Q} zz=`eioX-4wuePd|ycD!ivwz?MHV;Rj_*Xi;*|$`doY-pARa8fUFCCkc%q`#q>FMmE zex~?sq8SDR)=D)Eh2KW|L7kV{m=OE4g{bFQvxg!)K3L`crDn0QDl}!r0XyW6)#DhC zT=RbNqHM{ctyMuQ|4ht!q7JwMDHrtr%>6%D!i8A$^L}Mt@{AxN>6!#CE`iV%Q7!4& z{%#k+PnH7V4DlDl*4un1KaB3(le=j}yZw9O+y%Quy1~-nz>5v)7 zIG+ljb24Ts$@@UcB6k@NGPcrbG+jK(EArjStrsMN(kaDXlaP}-yJP7sddR$|e}!ZB z0I^%tJ7My8ogG?rJ3{7WRk|Cn9_mKTMK3l%`-+bLxz;%Uk6 z=g+dpea!n;z`dgyr7S~xJ$!RkT@(6XctL2IEiRhj-|HbxNbgOVeFtosF`o&e+9p7A z73)oj6|}51Uf@;nOAOLVT=OPNYRVIxB2or(%5S3mK=8My4GLx1LYQ)0hQA(R?wfdL zpD0*Q?d~z2HW@Y$sj-?B{S^ub0zX<7j(wd$|D7O+&;oy0&|BOl8Nn#z4p{*8(XxLR zePpe@yV!3#!@tHt_Ra-LQ)MH1i*E`OF6&H099Il<6r@CN{>73OGSBjM$cSTsui z`HA5L?vBSnW^AKYWUflqC&FrX9TG%UMgl3{M0XX{Aro2 z!&Ari7cJj-LS)Jt(gHuOA?9Wc6l!*P3T#-$q-x_>8huY<3}u|Gx~&o)2m~UiW#Q-= z`IhS*zg@Gq?6|4|%%LwraEVY8yYc7nTx7VqzGE$=>mK#Z&=27=T6XA{jW6+}gZAu# z2^N0;Jc!$PSZy$;j}~2;X=TqPJ89g!v`A7}P+p4tU;C$&1*aa~T+B z^+LUKW^IUN0UkL0(;IxTj`>Lv8^}6?Nz+|YP^%{mPfJ~gVQ?LK zTv9A2nU#?_9bYx!iYaJUPwDw8vcyf>Djgl_?^Ex3JjEXAwn^KTyN*XZC||j_4?=%` z()i`9won{5UpTP;v!6{SjK2KA3>{d0#A>dW9w_k%93)GurY~& zq;&^mnzGob=B#Dpv<-RWg?LK3iBE{CF8fmBJRlovm;^Tj2uHxqKg{}NCjOH{T9Fc_ zOK{J<2kakrSj9!aRN}oYRh){hZ6VJabphoZkb)_zi#OjnWT&3w!=~um_jaQxt(una zL>%rxhQB#ZcYY`{+!dJJNiT;khSvW9pc2QUME5M}^Yz!z`p{3P_p#T(_p8v;N6N)q zGwC13zM!b$(?@>49A3TJ0tE$~7rnc4=z6vGd8U&d6^qyebq=yQFS@<6I#0QY`Neh< z7L-zjvl;@E3`*ht8u81)=uEP0jo>CmtND6JDtqB!P|7}UM`(SM&+pd$l^+w|o}6NS zdc914_;tqXN885l`ft*8QZpa_ZxJs?k#r2)fAIliDn4H>$@pi~6e0KuH;>gs|yoJ&(@RG;)c!6Luoe%5|V0}!Xxn; z)q`0xi7-~l&m~^(-kZ^bX+4Hx2I~LqH2t4^ssD*kMqIGsf>CfB%=7-Wdm}k4r)_X) zB*%=_i_c%MR7M9~?R>epgqw~$-$tkOZLf8yix_brJ;GCLPzC5@ z^}Ta^>BRCh+L+GcYS0G}JzZxrfWMGke=N$0{>>c=Xr<4`ZfcR>AJt=0;R;kC4)3NO zFN*<~e$En_|IpCl^D!NMz>bXiZ6KO+nhax4Xs(DyE|SIXoJuroz*eqAY( z&&`8KB}6~Q$f%+ZXpboDe1NZ}Be+b#*xv@E3WCh$nY#zd>FS!9lh^sd=5lHrt<01w z!PeNZPpa4zB_nS}ch>}loR5|cs>)S6u>fSU!1#&G59lEjsX^@Q2W~NB`il)|F=~Z0 zHMvk{a63>&e@$7OU}Ux%+3tU$Vax6!>so~k&!H!jQUQ!ZofLRsIsOL5P@VLI#RfO7 z94?S#VMR{XV+q1U6&d12PUoO`j($8vkmN;RngExRL1E`9>m%TyCx^3hD@st|bMm~Q z0O;eFZUKM;DpicNkxUxNWcpTwweIraXA5Bl#BA0O5qZ2;8V{_E=}cGFA+iE<36XuP zsgWqovr0nS7gxB;9|x)mTnp+7S&c~z2yr4M2;FMD8JG0h*V2(ob*#gYzb=}!&)6S1 zm~)T%?2Cd6tt_$$qvpNjMtFlG=a72Ng;qvv8x136nbWZtLq=pK0zs1eLX;ATl^a@% zL2kYt0?B`Y9>CusZ+|q?R4$~NYarpQb3%5tt0xaQOvA$no%@D2JEcT%y{r*rgl8J_ z-Rfxxcn>ut4hSvN04~ivP>O+W1;r5BUy-AF0AnXjp9qcBg9a@FnwF(N>WQclQ(1D) zWedAH(?)D==T3`&?kJ6pfYLo+d6`E&TAf*nzYq9u)|gU2Ao?mg9Xky>EE{i|>6@~i z95rLTbE|V-_qA$3;Kqs3nvb2@d4R_=YE69#5;uI^B#{GV&3%oA^{ng{f~)%Uc*wSY2dl1TQ|UQ-?j>Z0x}EnM3E3bN6Mben|Vi%3CeFr#ElFA01C#=p+EgtV}QwEb5I?~t}l)IG}U zQgdHt{@)VY#V%jj*1`L00rt7a5Qg5vJa0_&$pK0aRNMUr>$_6ctH4}j>%1-5tCMGT zU-|E-LK0uSePZ+fwZL}}PRDN{4GJUpY3)yTA(`m;oj(YLVGui+(G2ujC@Q%A@PD5e zKaWYr{Z0>sp@J+|iMk&S=bZm1u+V=JaQ~q<{R>9)-?+luNd>bVM4(bhPUWJ4?)*28 z09#c2fQ3F`ZWl7934nav&O~2)!~rZca)9bxX{(}Xf@u(C=ta5(%T5V*jia|EB>b5f zZ;2RJ$!Qchk!Zhwu!(!7mJDdzgWv>x#EC1jl#;1YK0<@dr^r?^vzaSIypy2_E-}|iA?EYGV(eTD7r|83k8LMq-4sor z@U(Z8-HhbS*D?<#FnwUTQ$jp0>lI^mz$pbBv$cH|XB|V@A8rMsV}z6tF4$F*4qW^GxKjpiyuHHinR}>ps zdi35Kn+!x3JHR8E>hU&c1EMOKgVf;A*+KS}HWZ)mmH)(t)i*z+3?C%-3`e| Date: Fri, 2 Oct 2026 08:48:17 +0800 Subject: [PATCH 063/214] docs(forside): kort udgave + 24 sekunders film af vaerktoejerne + GitHub-stjerner Gustav: for meget tekst, vis stjerner, laengere hero-film med vaerktoejerne i aktion. - Research (Evil Martians, 100 devtool-forsider): centreret hero, kort undertekst, to knapper, animeret produkt-UI lige under, derefter tillid, faa punkter, stor slut-knap. - Synlig tekst 1.956 -> 651 ord (FAQ foldet ud: 2.365 -> 1.029). - Hero-film: 24 s i CSS paa een tidslinje, seks scener (screenshot med sloeret kodeord, menu, set_value + press bag dit vindue, wait_for, besked der kraever Touch ID, loggen), pause/afspil uden JS, statisk slutbillede ved reduced motion. - Stjernetal (1) bagt ind i headeren, ikke hentet live - sidens loefte om nul tredjepartskald holder. Co-Authored-By: Claude Opus 5.5 --- docs/index.html | 882 +++++++++++++++++++----------------------------- 1 file changed, 351 insertions(+), 531 deletions(-) diff --git a/docs/index.html b/docs/index.html index b8ee9359..b90dbb66 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,19 +6,19 @@ Computer MCP: computer use you can leave running - + - + - + @@ -27,8 +27,7 @@ -
+

404

@@ -103,13 +152,16 @@

The pages that do exist

diff --git a/docs/blog/driving-macos-from-an-ai-agent.html b/docs/blog/driving-macos-from-an-ai-agent.html index 66d8df63..1d3430ba 100644 --- a/docs/blog/driving-macos-from-an-ai-agent.html +++ b/docs/blog/driving-macos-from-an-ai-agent.html @@ -7,7 +7,8 @@ - + + @@ -30,45 +31,118 @@ "mainEntityOfPage":"https://computermcp.dev/blog/driving-macos-from-an-ai-agent.html"} -
- computermcp - +
@@ -160,6 +234,17 @@

What we would tell someone starting today

- + diff --git a/docs/blog/index.html b/docs/blog/index.html index 1fd0b3ec..6d67f254 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -8,7 +8,8 @@ - + + @@ -22,35 +23,118 @@ -
- computermcp - +
@@ -70,6 +154,17 @@

Driving macOS from an AI agent: what actually works

- + diff --git a/docs/blog/screenshot-password-leak.html b/docs/blog/screenshot-password-leak.html index a6e5e2c2..be3591d6 100644 --- a/docs/blog/screenshot-password-leak.html +++ b/docs/blog/screenshot-password-leak.html @@ -7,7 +7,8 @@ - + + @@ -30,45 +31,118 @@ "mainEntityOfPage":"https://computermcp.dev/blog/screenshot-password-leak.html"} -
- computermcp - +
@@ -164,6 +238,17 @@

How to check your own setup in two minutes

- + diff --git a/docs/docs/capability-matrix/index.html b/docs/docs/capability-matrix/index.html index 0955d8e4..84ffdad2 100644 --- a/docs/docs/capability-matrix/index.html +++ b/docs/docs/capability-matrix/index.html @@ -7,7 +7,8 @@ - + + @@ -20,43 +21,105 @@ -
- computermcp - +
@@ -163,6 +233,17 @@

Related

The failures that look like bugs and are not

- + diff --git a/docs/docs/install-claude-code/index.html b/docs/docs/install-claude-code/index.html index 6bcbe799..095f3209 100644 --- a/docs/docs/install-claude-code/index.html +++ b/docs/docs/install-claude-code/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / Claude Code

Install Computer MCP for Claude Code

@@ -209,13 +258,16 @@

Related

Troubleshooting
diff --git a/docs/docs/install-claude-desktop/index.html b/docs/docs/install-claude-desktop/index.html index 979d315b..ac190f38 100644 --- a/docs/docs/install-claude-desktop/index.html +++ b/docs/docs/install-claude-desktop/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / Claude Desktop

Install Computer MCP for Claude Desktop

@@ -236,13 +285,16 @@

Related

Threat model
diff --git a/docs/docs/install-codex/index.html b/docs/docs/install-codex/index.html index 39908fe6..186f5774 100644 --- a/docs/docs/install-codex/index.html +++ b/docs/docs/install-codex/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / OpenAI Codex

Install Computer MCP for OpenAI Codex

@@ -209,13 +258,16 @@

Related

Troubleshooting
diff --git a/docs/docs/install-copilot/index.html b/docs/docs/install-copilot/index.html index 2b22930f..24aed4a5 100644 --- a/docs/docs/install-copilot/index.html +++ b/docs/docs/install-copilot/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / GitHub Copilot

Install Computer MCP for GitHub Copilot

@@ -235,13 +284,16 @@

Related

Threat model
diff --git a/docs/docs/install-cursor/index.html b/docs/docs/install-cursor/index.html index cb348226..4923a263 100644 --- a/docs/docs/install-cursor/index.html +++ b/docs/docs/install-cursor/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / Cursor

Install Computer MCP for Cursor

@@ -222,13 +271,16 @@

Related

Troubleshooting
diff --git a/docs/docs/install-vscode/index.html b/docs/docs/install-vscode/index.html index e6242ae0..ddd6b572 100644 --- a/docs/docs/install-vscode/index.html +++ b/docs/docs/install-vscode/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Install / VS Code

Install Computer MCP for VS Code

@@ -225,13 +274,16 @@

Related

Threat model
diff --git a/docs/docs/uninstall/index.html b/docs/docs/uninstall/index.html index 39dd210b..6e659676 100644 --- a/docs/docs/uninstall/index.html +++ b/docs/docs/uninstall/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Docs / Uninstall

Uninstalling Computer MCP, and exactly what data it touches

@@ -171,13 +220,16 @@

Related

MCP security checklist
diff --git a/docs/docs/when-not-to-use/index.html b/docs/docs/when-not-to-use/index.html index e067bbe6..d85cd1d4 100644 --- a/docs/docs/when-not-to-use/index.html +++ b/docs/docs/when-not-to-use/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Docs / When not to use it

When NOT to use Computer MCP

@@ -176,13 +225,16 @@

Related

MCP security checklist
diff --git a/docs/learn/computer-use-mcp/index.html b/docs/learn/computer-use-mcp/index.html index ff0173b9..a5c9be11 100644 --- a/docs/learn/computer-use-mcp/index.html +++ b/docs/learn/computer-use-mcp/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Learn / Computer-use MCP servers

What is a computer-use MCP server?

@@ -265,13 +314,16 @@

Related

All 32 tools
diff --git a/docs/learn/mcp-security/index.html b/docs/learn/mcp-security/index.html index 0a81765e..b3b2ec4d 100644 --- a/docs/learn/mcp-security/index.html +++ b/docs/learn/mcp-security/index.html @@ -7,7 +7,8 @@ - + + @@ -20,49 +21,105 @@ -
- computermcp - +
@@ -203,6 +267,17 @@

Read next

Every tool this server exposes - the permission list, in full.

- + diff --git a/docs/learn/owasp-mcp-top-10/index.html b/docs/learn/owasp-mcp-top-10/index.html index c9081c99..9a79c9f1 100644 --- a/docs/learn/owasp-mcp-top-10/index.html +++ b/docs/learn/owasp-mcp-top-10/index.html @@ -7,7 +7,8 @@ - + + @@ -18,79 +19,119 @@ -
+

Computer MCP / Learn / OWASP MCP Top 10

The OWASP MCP Top 10, answered with running code

@@ -252,13 +293,16 @@

Related

When not to use it
diff --git a/docs/privacy.html b/docs/privacy.html index ecd9309f..6f9853a9 100644 --- a/docs/privacy.html +++ b/docs/privacy.html @@ -7,75 +7,124 @@ - + + -
+

Computer MCP / Privacy

@@ -145,12 +194,16 @@

Changes and contact

diff --git a/docs/security.html b/docs/security.html index 08262723..b3ba9c74 100644 --- a/docs/security.html +++ b/docs/security.html @@ -8,7 +8,8 @@ - + + @@ -22,47 +23,118 @@ -
- computermcp - +
@@ -142,6 +214,17 @@

Reporting something

Security reports: hello@agent360.dk, or a private advisory on the GitHub repository. If you find a way past a gate described on this page, that is exactly the thing we want to hear about - please say so before it is public, and we will credit you when it is fixed.

- + diff --git a/docs/tools.html b/docs/tools.html index 751cadd5..480cf4f3 100644 --- a/docs/tools.html +++ b/docs/tools.html @@ -8,7 +8,8 @@ - + + @@ -22,51 +23,118 @@ -
- computermcp - +
@@ -261,6 +329,17 @@

A working order

The last two are the ones agents skip and humans miss.

- + diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html index bae1de67..02ac9c81 100644 --- a/docs/troubleshooting.html +++ b/docs/troubleshooting.html @@ -8,7 +8,8 @@ - + + @@ -22,41 +23,118 @@ -
- computermcp - +
@@ -124,6 +202,17 @@

Still stuck

Open an issue with the output of computer_permissions, the last few lines of computer_audit, and what you expected to happen. The audit log holds no credential text, so it is safe to paste.

- + diff --git a/docs/use-cases/unattended-runs/index.html b/docs/use-cases/unattended-runs/index.html index c27fa6a9..b6ecd02b 100644 --- a/docs/use-cases/unattended-runs/index.html +++ b/docs/use-cases/unattended-runs/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Use cases / Unattended runs

Leaving an AI agent running on your Mac overnight

@@ -172,13 +221,16 @@

Related

Threat model
diff --git a/docs/use-cases/without-taking-over-your-screen/index.html b/docs/use-cases/without-taking-over-your-screen/index.html index 967f6bb6..d2d754c6 100644 --- a/docs/use-cases/without-taking-over-your-screen/index.html +++ b/docs/use-cases/without-taking-over-your-screen/index.html @@ -7,7 +7,8 @@ - + + @@ -18,71 +19,119 @@ -
+

Computer MCP / Use cases / Without taking over your screen

Let an agent work while you keep using your Mac

@@ -167,13 +216,16 @@

Related

Threat model
From a9de4875c204c67091c8ca5e1f48433fa27dab0b Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:15:18 +0200 Subject: [PATCH 096/214] =?UTF-8?q?side:=20undersiderne=20f=C3=A5r=20en=20?= =?UTF-8?q?visuel=20top=20som=20forsiden;=20kodeordsp=C3=A5standen=20rette?= =?UTF-8?q?t=20overalt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustav 6/10: «forbedre siderne og undersiderne med bedre visuelle blik som forsidens hero … i stedet for så meget tekst uden visualitet». Topsektion (v12/hero.py i plans, gentagelig): overskrift og indledning til venstre, et kort til højre bygget af sidens EGET indhold, så det ikke kan påstå andet end siden: installsiderne viser klientens egen kommando + computer_permissions-tjekket, tools viser de 32 værktøjer som chips (14 look / 18 touch, talt fra siden), matrixen mærkernes antal talt fra tabellen, security et skærmbillede hvor kodeordsfeltet er sort, privacy hvad der bliver på Mac'en, troubleshooting de første symptomer, og learn/blog/use cases en klikbar «On this page» (h2 får id'er). Fundet undervejs: «password managers ask every time, in every mode» stod stadig 41 steder på 11 undersider + llms-install.md (rettet på forsiden 3/10). Sandheden (policy.js:554-569): afvises i standardtilstanden, spørger hver gang kun med CMCP_BACKGROUND=0. Samme erstatninger overalt, også meta og ld+json (v12/kodeord-ret.py). Tjek: tjek-forside ALT GROENT, claims-offline BESTAAET, sync-tal idempotent, claims 15/15b/42a-d/45/47 isoleret OK, 603 links og #ankre / 0 mangler. Co-Authored-By: Claude Opus 5.5 --- docs/404.html | 41 ++++++++++- docs/blog/driving-macos-from-an-ai-agent.html | 55 ++++++++++++--- docs/blog/index.html | 47 +++++++++++-- docs/blog/screenshot-password-leak.html | 59 +++++++++++++--- docs/docs/capability-matrix/index.html | 43 +++++++++++- docs/docs/install-claude-code/index.html | 50 ++++++++++++-- docs/docs/install-claude-desktop/index.html | 50 ++++++++++++-- docs/docs/install-codex/index.html | 50 ++++++++++++-- docs/docs/install-copilot/index.html | 50 ++++++++++++-- docs/docs/install-cursor/index.html | 50 ++++++++++++-- docs/docs/install-vscode/index.html | 50 ++++++++++++-- docs/docs/uninstall/index.html | 55 ++++++++++++--- docs/docs/when-not-to-use/index.html | 63 +++++++++++++---- docs/learn/computer-use-mcp/index.html | 59 +++++++++++++--- docs/learn/mcp-security/index.html | 61 +++++++++++++--- docs/learn/owasp-mcp-top-10/index.html | 61 +++++++++++++--- docs/llms-install.md | 6 +- docs/privacy.html | 43 +++++++++++- docs/security.html | 43 +++++++++++- docs/tools.html | 43 +++++++++++- docs/troubleshooting.html | 43 +++++++++++- docs/use-cases/unattended-runs/index.html | 69 +++++++++++++++---- .../index.html | 59 +++++++++++++--- 23 files changed, 1001 insertions(+), 149 deletions(-) diff --git a/docs/404.html b/docs/404.html index 918b36c3..ded4aced 100644 --- a/docs/404.html +++ b/docs/404.html @@ -111,6 +111,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -130,7 +169,7 @@

404

That page is not here. Nothing was logged about your visit, which is rather the theme of this project.

-

The pages that do exist

+

The pages that do exist

Overview Install for Claude Code diff --git a/docs/blog/driving-macos-from-an-ai-agent.html b/docs/blog/driving-macos-from-an-ai-agent.html index 1d3430ba..d11a4121 100644 --- a/docs/blog/driving-macos-from-an-ai-agent.html +++ b/docs/blog/driving-macos-from-an-ai-agent.html @@ -130,6 +130,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -146,12 +185,12 @@
-

Driving macOS from an AI agent: what actually works

+

Driving macOS from an AI agent: what actually works

18 September 2026

-

Five findings from building a macOS computer-use server. Each one cost real time to learn, and each one looks like a bug in your code when it is not.

+

Five findings from building a macOS computer-use server. Each one cost real time to learn, and each one looks like a bug in your code when it is not.

On this page
-

1. The accessibility tree beats the screenshot, every time

+

1. The accessibility tree beats the screenshot, every time

The intuitive design is: screenshot, let the model look, let it say "click at 812, 460". It works in a demo and degrades immediately, because a coordinate is only true until something moves. A window shifts, a notification pushes a toolbar down, the display scale differs - and now the agent is clicking something else, confidently, without any signal that it missed.

@@ -164,7 +203,7 @@

1. The accessibility tree beats the screenshot, every time

Use the screenshot for what it is good at - letting the model understand the situation - and the tree for acting on it.

-

2. A full-screen app hides the entire rest of the machine

+

2. A full-screen app hides the entire rest of the machine

This one cost us half a day, and the symptom is the most misleading in the whole category: everything returns empty and nothing errors.

@@ -176,7 +215,7 @@

2. A full-screen app hides the entire rest of the machine

The second-order lesson is worth more than the first: a test that runs against the live screen can pass while measuring nothing. Ours did. A security test that goes green when it cannot see anything is worse than no test, so the ones that matter should build their own input instead of trusting the desktop.

-

3. In Electron apps, names are not unique - and it is not close

+

3. In Electron apps, names are not unique - and it is not close

Electron apps - VS Code, Slack, Discord, Notion, Spotify - expose good, pressable accessibility elements. They also duplicate names heavily, because a composer, a message list and a sidebar each contribute their own copy of the same control.

@@ -196,7 +235,7 @@

3. In Electron apps, names are not unique - and it is not close

One more Electron detail: labels often carry a trailing hint in the accessible name. Show command menu (/) is one string. An exact-title match on Show command menu finds nothing at all.

-

4. Web password fields are marked differently from native ones

+

4. Web password fields are marked differently from native ones

If you are redacting screenshots, or avoiding reading credential values, the marking differs by where the field lives:

@@ -207,7 +246,7 @@

4. Web password fields are marked differently from native ones

Check the role only and you handle native apps and miss every browser password box - which is the case that actually occurs. Check both. It is one extra line and it is the difference between a guarantee and a nearly-guarantee.

-

5. An error message that lies costs more than no error message

+

5. An error message that lies costs more than no error message

Ours said "the application is not running." The application was running perfectly well - it just had no window on the visible Space, so the capture API did not list it. That message sent us looking for something that was standing right in front of us.

@@ -219,7 +258,7 @@

5. An error message that lies costs more than no error message

Worth noticing where this was found: not by rereading the code, but by following a review protocol that insisted on walking the install path in a fresh clone. The same pass found a test that measured the machine instead of the code, and a binary we had accidentally committed. None of the three came from staring harder at the source.

-

What we would tell someone starting today

+

What we would tell someone starting today

  1. Build on the accessibility tree first. Add pixel clicking as the fallback for canvases and maps, not as the primary path.
  2. diff --git a/docs/blog/index.html b/docs/blog/index.html index 6d67f254..8c7e9935 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -122,6 +122,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -138,17 +177,17 @@
-

Writing

-

Notes from building a macOS computer-use MCP server. Measured findings, not opinions - each piece says how we know.

+

Writing

+

Notes from building a macOS computer-use MCP server. Measured findings, not opinions - each piece says how we know.

On this page
-

Your agent's screenshot tool is reading your passwords

+

Your agent's screenshot tool is reading your passwords

What is actually in a full-screen capture of a working developer's Mac, why redacting after writing the file is not redacting, and the one marking detail that breaks most implementations.

18 September 2026
-

Driving macOS from an AI agent: what actually works

+

Driving macOS from an AI agent: what actually works

Five findings: the accessibility tree over pixels, the full-screen Space that hides the whole machine, 73 buttons with 47 unique names, secure-field subroles, and why an error message that lies costs a day.

18 September 2026
diff --git a/docs/blog/screenshot-password-leak.html b/docs/blog/screenshot-password-leak.html index be3591d6..aa217579 100644 --- a/docs/blog/screenshot-password-leak.html +++ b/docs/blog/screenshot-password-leak.html @@ -130,6 +130,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -146,12 +185,12 @@
-

Your agent's screenshot tool is reading your passwords

+

Your agent's screenshot tool is reading your passwords

18 September 2026

-

If you have given an AI agent a screenshot tool on the Mac you actually work on, it has almost certainly seen something you would not have shown it. Not because it is malicious. Because a screenshot does not know what it contains.

+

If you have given an AI agent a screenshot tool on the Mac you actually work on, it has almost certainly seen something you would not have shown it. Not because it is malicious. Because a screenshot does not know what it contains.

On this page
-

What is actually in the picture

+

What is actually in the picture

Think about what is on your screen right now. Not what you are looking at - everything that is on it. On a working developer's machine, a full-screen capture routinely contains at least one of:

@@ -166,13 +205,13 @@

What is actually in the picture

Every one of those goes to the model as image data, and from there into whatever the conversation is stored in. Not stolen - just sent, because you asked for a screenshot and a screenshot is all of the screen.

-

Why "I'll just be careful" does not hold

+

Why "I'll just be careful" does not hold

The obvious answer is to close sensitive things first. It fails for a mundane reason: the agent decides when to take the screenshot, and it takes a lot of them. An agent working through a five-step task screenshots after every step. You cannot audit a moment you did not choose.

The second answer is to only screenshot one window. Better, and still leaky: the window you are automating is often the one with the login in it. That is usually why the agent is there.

-

Why redacting afterwards is not redacting

+

Why redacting afterwards is not redacting

The instinct when building this is to capture the image, then find the sensitive regions, then paint over them. It produces a correct-looking output file and it is wrong, for a reason that is easy to miss:

@@ -188,7 +227,7 @@

Why redacting afterwards is not redacting

The order has to be: capture into memory, find the regions, paint them, then encode and write. The unredacted bitmap should never exist anywhere but in the process's own memory. That is a small change to write and a large change to the guarantee.

-

The detail that breaks most implementations

+

The detail that breaks most implementations

macOS marks password fields for you, which makes this sound easy. It is easy, and there is exactly one trap, and nearly everyone falls into it.

@@ -202,7 +241,7 @@

The detail that breaks most implementations

We shipped that bug ourselves for about an hour. The test passed the whole time, because the test used a native field.

-

What a password manager needs is different

+

What a password manager needs is different

For a vault - 1Password, Bitwarden, Keychain Access - field-level redaction is the wrong shape entirely. There is no safe part of that window. Descending into a password manager's UI to work out which bits to hide is the same mistake in a smaller font.

@@ -210,18 +249,18 @@

What a password manager needs is different

That leaves a list of which apps count as vaults, and that list is the honest weak point of any tool that does this. Ours contains the password managers we thought of, in one country, on our own machines. It does not contain your bank, your company's secrets manager, or whatever is popular where you live. That is not a problem one team can fix by working harder - it is fixed by many people each adding one line, which is why there is an issue form for exactly that and it needs no code.

-

Black, not blurred

+

Black, not blurred

Blur and pixelation look thorough and are not. A model good enough to read your screen is often good enough to read through a light blur, and pixelation of a known font is a solvable puzzle. Opaque black is not a stronger version of blur - it is a different thing, and it is the only one with no recovery path.

-

What this still does not cover

+

What this still does not cover

Redaction covers what the operating system marks as secure. A password sitting in a plain text editor is not marked. A token in a terminal buffer is not marked. An API key in a config file on screen is not marked. No amount of care at this layer finds those, and a tool that claims otherwise is selling you something.

When the screen holds something the system cannot know is sensitive, the answer is a read-only mode or closing the window, not a cleverer filter.

-

How to check your own setup in two minutes

+

How to check your own setup in two minutes

  1. Open a sign-in page with a password field and type a few characters into it.
  2. diff --git a/docs/docs/capability-matrix/index.html b/docs/docs/capability-matrix/index.html index 84ffdad2..e4f4dafa 100644 --- a/docs/docs/capability-matrix/index.html +++ b/docs/docs/capability-matrix/index.html @@ -120,6 +120,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} + @@ -133,10 +172,9 @@
-

Computer MCP / Install / Claude Code

+

Computer MCP / Install / Claude Code

Install Computer MCP for Claude Code

-

Three steps, about two minutes. Claude Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. Claude Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

Claude Code · setup
$ claude mcp add --scope user computer -- npx -y @agent360/computer-mcp@latest
restart Claude Code, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -172,10 +210,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.
Evaluating, or the agent only needs to look. Start here.
askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched.
allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/install-claude-desktop/index.html b/docs/docs/install-claude-desktop/index.html index ac190f38..38efdac9 100644 --- a/docs/docs/install-claude-desktop/index.html +++ b/docs/docs/install-claude-desktop/index.html @@ -17,7 +17,7 @@ - + @@ -133,10 +172,9 @@

-

Computer MCP / Install / Claude Desktop

+

Computer MCP / Install / Claude Desktop

Install Computer MCP for Claude Desktop

-

Three steps, about two minutes. Claude Desktop drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. Claude Desktop drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

Claude Desktop · setup
$ ~/Library/Application Support/Claude/claude_desktop_config.json
restart Claude Desktop, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -186,10 +224,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.Evaluating, or the agent only needs to look. Start here. askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched. allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/install-codex/index.html b/docs/docs/install-codex/index.html index 186f5774..44bcb8df 100644 --- a/docs/docs/install-codex/index.html +++ b/docs/docs/install-codex/index.html @@ -17,7 +17,7 @@ - + @@ -133,10 +172,9 @@
-

Computer MCP / Install / OpenAI Codex

+

Computer MCP / Install / OpenAI Codex

Install Computer MCP for OpenAI Codex

-

Three steps, about two minutes. Codex drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. Codex drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

OpenAI Codex · setup
$ codex mcp add computer -- npx -y @agent360/computer-mcp@latest
restart OpenAI Codex, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -172,10 +210,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.Evaluating, or the agent only needs to look. Start here. askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched. allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/install-copilot/index.html b/docs/docs/install-copilot/index.html index 24aed4a5..226b03d9 100644 --- a/docs/docs/install-copilot/index.html +++ b/docs/docs/install-copilot/index.html @@ -17,7 +17,7 @@ - + @@ -133,10 +172,9 @@
-

Computer MCP / Install / GitHub Copilot

+

Computer MCP / Install / GitHub Copilot

Install Computer MCP for GitHub Copilot

-

Three steps, about two minutes. Copilot in VS Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. Copilot in VS Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

GitHub Copilot · setup
{
"servers": {
"computer": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@agent360/computer-mcp@latest"]
restart GitHub Copilot, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -184,10 +222,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.Evaluating, or the agent only needs to look. Start here. askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched. allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/install-cursor/index.html b/docs/docs/install-cursor/index.html index 4923a263..87eec323 100644 --- a/docs/docs/install-cursor/index.html +++ b/docs/docs/install-cursor/index.html @@ -17,7 +17,7 @@ - + @@ -133,10 +172,9 @@
-

Computer MCP / Install / Cursor

+

Computer MCP / Install / Cursor

Install Computer MCP for Cursor

-

Three steps, about two minutes. Cursor drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. Cursor drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

Cursor · setup
{
"mcpServers": {
"computer": {
"command": "npx",
"args": ["-y", "@agent360/computer-mcp@latest"]
}
restart Cursor, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -179,10 +217,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.Evaluating, or the agent only needs to look. Start here. askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched. allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/install-vscode/index.html b/docs/docs/install-vscode/index.html index ddd6b572..de8573d8 100644 --- a/docs/docs/install-vscode/index.html +++ b/docs/docs/install-vscode/index.html @@ -17,7 +17,7 @@ - + @@ -133,10 +172,9 @@
-

Computer MCP / Install / VS Code

+

Computer MCP / Install / VS Code

Install Computer MCP for VS Code

-

Three steps, about two minutes. VS Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except password managers and anything that deletes.

- +

Three steps, about two minutes. VS Code drives your real Mac with password fields blacked out before any screenshot is written, and it works without asking - except anything that deletes, and it refuses password managers.

VS Code · setup
$ code --add-mcp '{"name":"computer","command":"npx","args":["-y","@agent360/computer-mcp@latest"]}'
restart VS Code, then ask:
› call computer_permissions
{ "accessibility": true, "screenRecording": true }

What you get today, honestly. npx currently serves @@ -183,10 +221,10 @@

Step 1b: pick the mode before the first run

not exist as far as the model is concerned.Evaluating, or the agent only needs to look. Start here. askThe first write opens a dialog; one yes grants the session. -Password managers ask every single time, terminals and editors once per session.You want +Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session.You want to approve before anything is touched. allowThe default. Writes proceed without asking, still logged. -Password managers still ask every time, terminals and editors once per session - not +Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session - not configurable.Normal use: an agent you can leave running.

To start read-only:

diff --git a/docs/docs/uninstall/index.html b/docs/docs/uninstall/index.html index 6e659676..c3859cdb 100644 --- a/docs/docs/uninstall/index.html +++ b/docs/docs/uninstall/index.html @@ -118,6 +118,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -133,11 +172,11 @@
-

Computer MCP / Docs / Uninstall

+

Computer MCP / Docs / Uninstall

Uninstalling Computer MCP, and exactly what data it touches

-

Three steps to remove it, the one file it ever writes, what each macOS permission is for, and the thing that matters most: it makes no network calls at all, and you can check that yourself in one command.

+

Three steps to remove it, the one file it ever writes, what each macOS permission is for, and the thing that matters most: it makes no network calls at all, and you can check that yourself in one command.

On this page
-

Remove it in three steps

+

Remove it in three steps

  1. Unregister it from your MCP client. claude mcp remove computer for Claude Code, codex mcp remove computer for Codex, or delete the @@ -157,7 +196,7 @@

    Remove it in three steps

    and npm cache clean --force clears it, though that affects every package and is rarely worth doing for this reason alone.

    -

    Exactly what it touches while installed

    +

    Exactly what it touches while installed

    @@ -181,7 +220,7 @@

    Exactly what it touches while installed

    folder, no crash reporter. If you would rather check than trust: the only write in the whole source is in mcp-server/audit.js, and it writes to that one path.

    -

    The part that matters: nothing is sent to us

    +

    The part that matters: nothing is sent to us

    There is no account, no API key, no licence check, no telemetry and no update ping. The server makes no network calls of its own at all. The only thing that talks to a model is the MCP client you were already running, and it talks to whichever provider you already chose.

    @@ -194,7 +233,7 @@

    The part that matters: nothing is sent to us

    socket. If you ever find an actual outbound call in there, that is a bug report we would want immediately, and we would treat it as the most serious kind there is.

    -

    Before you go

    +

    Before you go

    If you are uninstalling because something did not work, troubleshooting covers the failures that look like bugs and are not, chiefly a full-screen app hiding every other window from both the accessibility @@ -204,7 +243,7 @@

    Before you go

    issue telling us what happened is worth more to us than a silent removal.

    -

    FAQ

    +

    FAQ

    How do I completely remove Computer MCP?

    Three steps: unregister it from your MCP client (claude mcp remove computer, codex mcp remove computer, or delete the block from ~/.cursor/mcp.json), delete the directory ~/.local/state/computer-mcp, and revoke the Accessibility and Screen Recording permissions if nothing else needs them. There is no installer, daemon or login item.

    What files does it leave on my Mac?

    Exactly one: ~/.local/state/computer-mcp/audit.jsonl, a log that only grows, mode 0600. No preferences file, no database, no hidden directory, no crash reporter. The only write in the entire source is in mcp-server/audit.js.

    Is my typed text stored in the log?

    Not in clear. Each typed action is recorded as a scrambled fingerprint, with a random ingredient so it cannot be looked up. The salt is random per run and never written down, so two actions compare within a run and not across runs. Without that random ingredient, a short password could be worked out offline by whoever ended up with the log, which is why it is salted.

    @@ -212,7 +251,7 @@

    FAQ

    Will uninstalling delete my audit history?

    Deleting the state directory does, and nothing else will. If you want to keep the record, copy the file somewhere first. It is plain JSON Lines and reads fine in any text editor.

    Do I have to revoke the macOS permissions?

    Only if nothing else needs them. They are granted to whichever app was the responsible process, often your terminal or your MCP client, and other tools may rely on the same grant. Removing the server does not use them either way.

    Can I reinstall later without redoing the permissions?

    Usually yes, if you grant them to the same app. We have not measured whether upgrading the package re-prompts: the helper is ad-hoc signed, so its code identity changes with every build, and if macOS keys the grant to the helper rather than the host app an upgrade could silently revoke it. Issue #4 tracks that measurement.

    -

    Related

    +
    What it can and cannot reach Threat model Install for Claude Code diff --git a/docs/docs/when-not-to-use/index.html b/docs/docs/when-not-to-use/index.html index d85cd1d4..bdd0691f 100644 --- a/docs/docs/when-not-to-use/index.html +++ b/docs/docs/when-not-to-use/index.html @@ -118,6 +118,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -133,9 +172,9 @@
    -

    Computer MCP / Docs / When not to use it

    +

    Computer MCP / Docs / When not to use it

    When NOT to use Computer MCP

    -

    It is not a sandbox, it does not run in CI, it is macOS only, and for anything in a browser there is a better tool. The honest limits, from the people who built it.

    +

    It is not a sandbox, it does not run in CI, it is macOS only, and for anything in a browser there is a better tool. The honest limits, from the people who built it.

    On this page

    We would rather you find this page before you install than after. Every item below is a @@ -143,7 +182,7 @@

    When NOT to use Computer MCP

    deliberate design choice we say so, and where it is simply not measured yet we say that too.

    -

    Do not use it when you need containment

    +

    Do not use it when you need containment

    This is the big one, and it is the thing most easily misread about this product. Consent is not containment. The gate decides whether an action happens. Once you approve it, the agent is driving your real Mac, with your real logged-in sessions, your @@ -153,20 +192,20 @@

    Do not use it when you need containment

    "will it ask me first" and becomes "what can it reach at all", which is a much better question to be able to answer.

    -

    Do not use it on a server, in CI, or over SSH

    +

    Do not use it on a server, in CI, or over SSH

    It needs a Mac with a real logged-in graphical session, a screen that is awake, and two permissions that a human granted by clicking. None of those exist on a build runner. Over SSH there is no window server to talk to, so both the accessibility API and the screenshot return nothing useful. This is not a gap we intend to close: the product assumes a person's own machine, and most of the safety design only makes sense there.

    -

    Do not use it on Windows or Linux

    +

    Do not use it on Windows or Linux

    There is no build, and there is no plan for one. The three guarantees are built directly on macOS APIs: the Accessibility API for knowing that a field is a password field, and ScreenCaptureKit for the picture. A port would be a different product with different guarantees, not this one recompiled.

    -

    Do not use it to read a secret off the screen

    +

    Do not use it to read a secret off the screen

    It cannot, and that is the point. Secure fields are blacked out before the image is written, and computer_inspect never returns their value. If your task depends on an agent reading a password back, this is the wrong tool and you should be glad it is.

    @@ -179,7 +218,7 @@

    Do not use it to read a secret off the screen

    good as the application's own field types, which is very good in native apps and login forms, and worth nothing in a text file.

    -

    Do not use it for web automation

    +

    Do not use it for web automation

    You can drive a browser through it, and you will have a worse time than you need to. Clicking a web page through the accessibility tree means fighting a layer that was built for screen readers, not for automation, and you lose the DOM entirely. For anything that lives @@ -187,20 +226,20 @@

    Do not use it for web automation

    same licence, and it drives your real signed-in Chrome with the page structure intact. The two are complementary, not competing.

    -

    Do not expect it to see behind a full-screen app

    +

    Do not expect it to see behind a full-screen app

    A macOS app in true full-screen mode gets its own Space, and from outside that Space both the accessibility API and the screenshot see nothing of the windows underneath. This looks like a bug the first time and it is not one; it is how Spaces work. Leave the app windowed, or bring the target Space forward first. This is the single most common thing people report as broken. Troubleshooting has the details.

    -

    Do not install it expecting a shell

    +

    Do not install it expecting a shell

    No shell execution, no arbitrary file reads, no URL fetching. Each would be a few lines of code, and each is left out on purpose: a computer-control server with a shell inside it is remote access under a friendlier name. If you want a shell, install a shell MCP server. Then you have chosen it, and the choice is visible in your config where you can see it.

    -

    So when is it the right tool?

    +

    So when is it the right tool?

    When the work is in native macOS applications, on the machine you actually use, and the reason you have not automated it already is that you did not want to hand an agent an unguarded pointer to that machine. That is the case this was built for: the long unattended @@ -209,7 +248,7 @@

    So when is it the right tool?

    And when it is not that, one of the tools above is better, and we would rather say so here than have you find out two hours in.

    -

    FAQ

    +

    FAQ

    Is Computer MCP a sandbox?

    No. It is a consent gate and an audit log, not containment. Once you approve an action the agent drives your real Mac with no isolation underneath. If you need a blast radius, run it against a virtual machine or a separate user account.

    Can I use it in CI or on a headless Mac?

    No. It needs a logged-in graphical session, an awake screen, and two macOS permissions a human granted by clicking. None of those exist on a build runner, and over SSH there is no window server, so both the accessibility API and the screenshot come back empty.

    Will there be a Windows or Linux version?

    No, and there is no plan for one. The guarantees are built on macOS APIs: one macOS system for knowing a box is a password box, and another for taking the picture. A port would be a different product with different guarantees.

    @@ -217,7 +256,7 @@

    FAQ

    Does it hide every secret on my screen?

    No. Only what macOS itself marks as secure: password fields in native apps and login forms, plus whole windows of listed password managers. A password in a plain text editor, an API key in a terminal buffer or a token in a devtools panel carries no such marking and is not hidden.

    Should I use this or Browser MCP for web tasks?

    Browser MCP, without hesitation. Driving a web page through the accessibility tree means fighting a layer built for screen readers and losing the DOM. Browser MCP drives your real signed-in Chrome with the page structure intact. Same people, same licence.

    The agent cannot see a window that is clearly open. Why?

    Most likely an app is in true full-screen mode, which puts it on its own Space. From outside that Space, neither the accessibility API nor the screenshot can see the windows underneath. Leave the app windowed or bring that Space forward first.

    -

    Related

    +
    What it can and cannot reach Threat model Install for Claude Code diff --git a/docs/learn/computer-use-mcp/index.html b/docs/learn/computer-use-mcp/index.html index a5c9be11..b1d0e577 100644 --- a/docs/learn/computer-use-mcp/index.html +++ b/docs/learn/computer-use-mcp/index.html @@ -118,6 +118,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -133,9 +172,9 @@
    -

    Computer MCP / Learn / Computer-use MCP servers

    +

    Computer MCP / Learn / Computer-use MCP servers

    What is a computer-use MCP server?

    -

    How computer-use MCP servers actually work on macOS - the accessibility tree versus pixels versus a shell, five things that are harder than they look, and seven questions worth asking before you let one run on the machine you work on.

    +

    How computer-use MCP servers actually work on macOS - the accessibility tree versus pixels versus a shell, five things that are harder than they look, and seven questions worth asking before you let one run on the machine you work on.

    On this page

    An MCP server is a small program that gives an AI assistant a set of tools. A computer-use MCP server gives it tools that operate the computer @@ -149,7 +188,7 @@

    What is a computer-use MCP server?

    the machine you work on. Most of the numbers below were measured while building one, and where something is unmeasured it says so.

    -

    What "computer use" means in practice

    +

    What "computer use" means in practice

    Three different mechanisms hide behind the same phrase, and they fail in different ways. Knowing which one a server uses tells you most of what you need to know about it.

    @@ -178,7 +217,7 @@

    What "computer use" means in practice

    server that has it should say so on the first screen rather than in a tool list you read later.

    -

    Five things that are harder than they look

    +

    Five things that are harder than they look

    These are not theoretical. Each one was measured while building a server, and each produced a wrong answer before it produced a right one.

    @@ -223,7 +262,7 @@

    5. Consent is not containment

    not a sandbox, and a server that implies otherwise is overselling. If you need a blast radius rather than a record, run the agent against a virtual machine.

    -

    What to check before you install one

    +

    What to check before you install one

    Seven questions. They take about ten minutes and they are the same questions regardless of which server you end up with.

    WhatWhy, and what is in it
    ~/.local/state/computer-mcp/audit.jsonl
    @@ -256,7 +295,7 @@

    What to check before you install one

    The difference decides whether you can keep working while it runs.
    -

    The field, as of September 2026

    +

    The field, as of September 2026

    A dozen or so macOS computer-use MCP servers exist. Measured on GitHub on 18 September 2026: the largest has around 5,000 stars and is actively pushed; two of the next three had not been pushed in 5 and 15 months respectively; the @@ -278,7 +317,7 @@

    The field, as of September 2026

    anything that lives in a web page.

-

Where this goes next

+

Where this goes next

The interesting gap is not capability, it is unattended capability. Almost any of these servers can do a task while you watch. Far fewer are designed for the case where you start something and go to lunch, which is where @@ -293,10 +332,10 @@

Where this goes next

what macOS marks as secret. It cannot cover what you left sitting in a text window.

-

Against the published taxonomy

+

Against the published taxonomy

The seven questions above are ours. If you would rather work from a published list, OWASP has one: the OWASP MCP Top 10, answered with running code walks all ten categories and marks the four where the honest answer is "not our layer" or "this cannot be closed".

-

FAQ

+

FAQ

What is a computer-use MCP server?

A small program that gives an AI assistant tools to operate a computer: read what is on screen, click, type, press buttons, choose menu items. It uses the standard that assistants use to find tools, so any of them - Claude Code, Cursor, Codex, VS Code - can use it. It runs on your machine, not in a data centre.

How is it different from browser automation?

A browser tool drives web pages and keeps the DOM, which is a far better handle than pixels. A computer-use server drives the operating system: native apps, menus, dialogs, the Finder. If the task lives in a web page, use a browser tool; driving a page through the accessibility tree means fighting a layer built for screen readers and losing the DOM.

Is it safe to let an AI agent control my Mac?

It depends on what the server does by default and what you have on screen. The useful questions are: does it have a read-only mode, what happens to password fields in a screenshot, what happens if nobody answers a consent dialog, and what is in the log. Note that consent is not containment: once you approve an action the agent drives your real machine with no isolation underneath. For a real blast radius, use a virtual machine.

@@ -305,7 +344,7 @@

FAQ

Can it work in the background while I keep using my Mac?

Partly, and it depends on the tools. Pressing an element through its own accessibility action reaches a window behind another one and never moves your pointer; so does writing into a field through the same API. Coordinate clicking and typing use the real cursor and keyboard, so those you will see. Anything with no accessibility element behind it - dragging on a canvas - needs the real pointer.

What runs on Windows or Linux?

Not the macOS servers. Their guarantees are built on macOS APIs: the Accessibility API for knowing that a field is a password field, and ScreenCaptureKit for the image. A port would be a different product with different guarantees, not the same one recompiled.

How do I install one?

Register it with your MCP client, then grant the operating system permissions it needs - on macOS, Accessibility and Screen Recording, which only a person can click. A server that claims to be installed before those are granted is not finished installing.

-

Related

+
Computer MCP What an agent can and cannot reach on macOS MCP security: what a server can reach diff --git a/docs/learn/mcp-security/index.html b/docs/learn/mcp-security/index.html index b3b2ec4d..fbd9dd46 100644 --- a/docs/learn/mcp-security/index.html +++ b/docs/learn/mcp-security/index.html @@ -120,6 +120,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} + @@ -133,9 +172,9 @@
-

Computer MCP / Learn / OWASP MCP Top 10

+

Computer MCP / Learn / OWASP MCP Top 10

The OWASP MCP Top 10, answered with running code

-

All ten OWASP MCP risk categories, and what one macOS computer-use server actually does about each - including the four where the honest answer is "not our layer" or "this one cannot be closed".

+

All ten OWASP MCP risk categories, and what one macOS computer-use server actually does about each - including the four where the honest answer is "not our layer" or "this one cannot be closed".

On this page

The OWASP MCP Top 10 (2025) is the first dedicated OWASP list for Model Context Protocol @@ -150,7 +189,7 @@

The OWASP MCP Top 10, answered with running code

is marketing with footnotes.

-

The ten, and what we do about each

+

The ten, and what we do about each

@@ -198,7 +237,7 @@

The ten, and what we do about each

can tell it to do something you did not ask for. We do not claim to prevent that, and no MCP server can. What the design does is make it visible rather than impossible: writes pass a consent gate that names the app, password -managers ask every single time regardless of mode, terminals and editors once per session, menu items that +managers are refused in the default mode (with CMCP_BACKGROUND=0 they ask every single time), terminals and editors ask once per session, menu items that look destructive ask every time, and everything lands in a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where you can read afterwards. That is a different promise from prevention and it is the one we make. @@ -235,7 +274,7 @@

The ten, and what we do about each

the application's own field types.
#OWASP categoryWhat this server does
-

What the scorecard actually says

+

What the scorecard actually says

Six addressed, two that are not this layer's problem, one with a different threat model, and one - prompt injection - that nobody can close and we do not claim to. That last row is the one worth dwelling on, because a security page @@ -247,7 +286,7 @@

What the scorecard actually says

did. None of that is prevention. All of it is the difference between an incident you can reconstruct and one you cannot.

-

How to check any of this yourself

+

How to check any of this yourself

None of the claims above need to be taken on trust.

git clone https://github.com/Agent360dk/computerMCP
 cd computerMCP
@@ -268,7 +307,7 @@ 

How to check any of this yourself

If it ever comes back with a socket, that is the bug report we would want fastest.

-

Other taxonomies worth knowing

+

Other taxonomies worth knowing

OWASP's is the most citable because it is numbered, but it is not the only one. The Coalition for Secure AI published an MCP security white paper through OASIS in January 2026 with a broader taxonomy - close to forty threats across @@ -277,15 +316,15 @@

Other taxonomies worth knowing

MCP security checklist is the practical middle: ten things to run against every server you already have installed.

-

FAQ

+

FAQ

What is the OWASP MCP Top 10?

A list of ten risk categories for Model Context Protocol implementations, published by OWASP in 2025 as the first OWASP project dedicated to MCP. It covers token mismanagement, privilege escalation, tool poisoning, supply chain attacks, command injection, prompt injection through context, weak authentication, missing audit trails, shadow servers, and context over-sharing.

Does Computer MCP address all ten?

No, and a server that claimed to would be overselling. Six are addressed, two are not this layer's problem (tool poisoning by other servers, shadow servers across a fleet), one has a different threat model (there is no network listener to authenticate to), and one - prompt injection - cannot be closed by any MCP server. We make it visible rather than impossible.

-
Can an MCP server prevent prompt injection?

No. A page or document the agent reads can always tell it to do something the person did not ask for. What a server can do is make the consequences visible: a consent gate on writes that names the target app, password managers that ask every single time regardless of mode, terminals and editors that ask once per session, and a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where you can read afterwards. That is reconstruction, not prevention.

+
Can an MCP server prevent prompt injection?

No. A page or document the agent reads can always tell it to do something the person did not ask for. What a server can do is make the consequences visible: a consent gate on writes that names the target app, password managers that are refused in the default mode and ask every single time with CMCP_BACKGROUND=0, terminals and editors that ask once per session, and a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where you can read afterwards. That is reconstruction, not prevention.

What is the most common MCP vulnerability in practice?

Command injection. 43% of MCP CVEs in January and February 2026 were of that class. It is worth asking any server whether it exposes a shell, and if it builds any command from text, whether that construction is tested against break-out payloads rather than merely reviewed.

Why does the audit log store hashes instead of the text?

Because a complete record of every keystroke is exactly the file an attacker would want, which would make the log the very thing the product is against. The hash proves two actions typed the same thing without storing what it was. It is scrambled with a random ingredient that only exists while it runs, because a fingerprint without that random ingredient, of a short password, can be guessed offline. The cost is honest: comparison works within a run, not across runs.

Does redaction cover every secret on screen?

No. Only what macOS itself marks as secure: password fields in native apps and login forms, plus whole windows of listed password managers. A password in a plain text editor, an API key in a terminal buffer or a token in a devtools panel carries no such marking and is not hidden. Before a long unattended run, that is the thing to look at - not the software, your own screen.

How can I verify these claims?

Clone the repository and run the test suite; every claim on this page points at either a measurement or a test. For the dependency count, read package.json and Package.swift. For outbound network calls, grep the source for fetch, http, net and axios - what comes back should be bundle identifiers and documentation links, nothing that opens a socket.

-

Related

+
MCP security checklist What is a computer-use MCP server? Our threat model diff --git a/docs/llms-install.md b/docs/llms-install.md index 69e878fc..aa5a2e2a 100644 --- a/docs/llms-install.md +++ b/docs/llms-install.md @@ -51,14 +51,14 @@ client's MCP config: ## Step 2 - pick the mode BEFORE the first run This is the decision that matters. The default is `allow`, so an agent can be left -running; password managers, terminals, quitting and closing still ask. Set +running; terminals, quitting and closing still ask, and password managers are refused. Set `CMCP_MODE` in the server's `env` block: | `CMCP_MODE` | What the agent can do | Use it when | |---|---|---| | `readonly` | Only the 14 read tools are listed. The write tools do not exist as far as the model is concerned. | You are evaluating, or the agent only needs to look. **Start here.** | -| `ask` | The first write opens a dialog; one yes grants the session. Password managers ask every single time, terminals and editors once per session. | You want to approve before anything is touched. | -| `allow` | **Default.** Writes proceed without asking. Still logged. Password managers still ask every time, terminals and editors once per session. | Normal use: an agent you can leave running. | +| `ask` | The first write opens a dialog; one yes grants the session. Password managers are refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session. | You want to approve before anything is touched. | +| `allow` | **Default.** Writes proceed without asking. Still logged. Password managers are still refused (with CMCP_BACKGROUND=0 they ask every time), terminals and editors ask once per session. | Normal use: an agent you can leave running. | Recommend `readonly` for a first install and say why: nothing can go wrong, and the user can see the tool list before granting anything. diff --git a/docs/privacy.html b/docs/privacy.html index 6f9853a9..d8ff90cf 100644 --- a/docs/privacy.html +++ b/docs/privacy.html @@ -111,6 +111,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -127,11 +166,11 @@
-

Computer MCP / Privacy

+

Computer MCP / Privacy

Privacy

This page is short because there is very little to say. Computer MCP has no account, no API key, no telemetry and no server of ours. It makes no network calls of its -own, and neither does this website.

+own, and neither does this website.

On your Mac
Network calls of its own0
Account or API keynone
Telemetry or analyticsnone
Audit log~/.local/state/computer-mcp/audit.jsonl
Who can read the logyour user (0600)

The one-line version: we collect nothing, because there is nothing built into the diff --git a/docs/security.html b/docs/security.html index b3ba9c74..e6032787 100644 --- a/docs/security.html +++ b/docs/security.html @@ -122,6 +122,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -138,8 +177,8 @@

-

Security model

-

What this defends against, how each gate actually works, and - the part most pages skip - what it does not protect you from.

+

Security model

+

What this defends against, how each gate actually works, and - the part most pages skip - what it does not protect you from.

screenshot, before it is written
Sign in
Emailyou@example.com
Password••••••••••
capture → find secure fields → paint them black → only then write the PNG

The threat we built for

An agent with a screenshot tool and a keyboard on a working developer's Mac. Not a hostile user: a useful agent, on a machine that happens to also hold an SSH key, a password manager, a customer database and a production terminal. The failure modes are mundane and they are why people don't run these tools where the work is:

diff --git a/docs/tools.html b/docs/tools.html index 480cf4f3..123248c1 100644 --- a/docs/tools.html +++ b/docs/tools.html @@ -122,6 +122,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -138,8 +177,8 @@
-

Tool reference

-

thirty-two tools. fourteen look, eighteen touch. The useful part of this page is not the parameter lists - it is knowing which tool to reach for when the obvious one is the wrong one.

+

Tool reference

+

thirty-two tools. fourteen look, eighteen touch. The useful part of this page is not the parameter lists - it is knowing which tool to reach for when the obvious one is the wrong one.

32 tools
permissionsappswindowsset_valuerequest_screenask_userpastelaunchopenquitpendingdragspacewindowmenusmenudisplaysscreenshotrecordwait_forfocusedinspectfindauditlearningpressclickmovescrolltypekeyactivate
● 14 look● 18 touch

What you get today, honestly. npx @agent360/computer-mcp currently serves 0.1.0, which has 12 tools. The 32 tools described here are the source: they are built and tested, but not published yet. Building from source takes about thirty-five seconds if you want them now.

diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html index 02ac9c81..923483c7 100644 --- a/docs/troubleshooting.html +++ b/docs/troubleshooting.html @@ -122,6 +122,45 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* topsektion v13 */ +main.wrap{max-width:1120px} +main.wrap>*:not(.subhero){max-width:820px} +.subhero{display:grid;grid-template-columns:minmax(0,1fr);gap:28px;align-items:center;margin:0 0 36px;padding-bottom:36px;border-bottom:1px solid var(--rule-soft)} +@media (min-width:960px){.subhero{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:48px}} +.subhero .lede{margin-bottom:0} +.viz{margin:0;background:var(--surface);border:1px solid var(--rule);border-radius:14px;overflow:hidden; + box-shadow:0 1px 0 rgba(22,24,29,.04),0 12px 32px -12px rgba(22,24,29,.28);min-width:0} +.viz .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--rule-soft);background:var(--surface-2);font-size:13px;color:var(--muted)} +.viz .bar i{width:11px;height:11px;border-radius:50%;background:var(--rule)} +.viz .bar span{margin-left:8px} +.viz .in{padding:16px 18px;display:flex;flex-direction:column;gap:10px} +.term{background:#14161A;color:#E6E8EC;font:13.5px/1.6 var(--mono);padding:16px 18px;display:flex;flex-direction:column;gap:6px;overflow-x:auto} +.term .p{color:#7FD4A0}.term .m{color:#9AA2AE}.term .o{color:#FFD58A} +.chipgrid{display:flex;flex-wrap:wrap;gap:6px} +.chipgrid span{font:600 12px/1 var(--mono);padding:6px 9px;border-radius:999px;border:1px solid var(--rule);white-space:nowrap} +.chipgrid .r{color:var(--green);border-color:var(--green)} +.chipgrid .w{color:var(--orange);border-color:var(--orange)} +.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px;color:var(--muted)} +.marks{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px} +.marks div{border:1px solid var(--rule-soft);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:6px} +.marks b{font-size:30px;line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums} +.facts{display:grid;gap:0} +.facts div{display:flex;justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.facts div:last-child{border-bottom:0} +.facts b{font-variant-numeric:tabular-nums;text-align:right} +.toc{list-style:none;margin:0;padding:0;counter-reset:t} +.toc li{counter-increment:t;display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;padding:9px 0;border-bottom:1px solid var(--rule-soft);font-size:15px} +.toc li:last-child{border-bottom:0} +.toc li::before{content:counter(t);font:600 12px/1.9 var(--mono);color:var(--muted)} +.toc a{color:var(--ink);text-decoration:none} +.toc a:hover{text-decoration:underline} +.shot{position:relative;padding:18px;display:flex;flex-direction:column;gap:12px} +.shot .field{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--muted)} +.shot .field span{height:38px;border:1px solid var(--rule);border-radius:8px;background:var(--surface-2);display:flex;align-items:center;padding:0 12px;font:14px var(--mono);color:var(--ink)} +.shot .field.secret span{background:#000;border-color:#000;color:#000} +.shot .note{font:12px/1.4 var(--mono);color:var(--muted)} +.sym-cards{display:grid;gap:8px} +.sym-cards div{border:1px solid var(--rule-soft);border-left:3px solid var(--orange);border-radius:10px;padding:10px 12px;font-size:14px} @@ -138,8 +177,8 @@
-

Troubleshooting

-

Every failure on this page is one we hit ourselves building the thing. Most of them are not bugs, and all of them look like bugs the first time.

+

Troubleshooting

+

Every failure on this page is one we hit ourselves building the thing. Most of them are not bugs, and all of them look like bugs the first time.

Symptoms on this page
No windows, no elements, no matches - but the tools report success.
A handful of nodes, no buttons - while other apps return hundreds.
"Accessibility access missing" even though the checkbox is on.
Screenshots come back with redactedRegions: 0.

Everything returns empty and nothing errors

No windows, no elements, no matches - but the tools report success.

diff --git a/docs/use-cases/unattended-runs/index.html b/docs/use-cases/unattended-runs/index.html index b6ecd02b..3dce7b49 100644 --- a/docs/use-cases/unattended-runs/index.html +++ b/docs/use-cases/unattended-runs/index.html @@ -9,15 +9,15 @@ - + - + - + @@ -133,9 +172,9 @@
-

Computer MCP / Use cases / Unattended runs

+

Computer MCP / Use cases / Unattended runs

Leaving an AI agent running on your Mac overnight

-

What makes a long unattended run survivable: password fields blacked out before the screenshot is written, password managers that ask every time in every mode, an unanswered dialog that counts as a refusal, and a log you can read back in the morning.

+

What makes a long unattended run survivable: password fields blacked out before the screenshot is written, password managers that are refused rather than asked, an unanswered dialog that counts as a refusal, and a log you can read back in the morning.

On this page

This is the case the product was built for, and it is worth being precise about what @@ -144,7 +183,7 @@

Leaving an AI agent running on your Mac overnight

nothing bad can happen, because it is driving your real machine.

-

What makes a long run survivable

+

What makes a long run survivable

1. Password fields were never in the picture

The single biggest risk in a long unattended run is not a wrong click. It is that @@ -157,10 +196,10 @@

1. Password fields were never in the picture

long run, that is the thing to look at: not whether the tool redacts, but what is sitting visible on your screen that macOS does not consider a secret.

-

2. Password managers ask every time, no matter the mode

+

2. Password managers are never approved in passing

Session consent is a convenience, and convenience is exactly what you do not want on a -password manager. Password managers ask on every single action, in every mode, -including allow. That is not configurable, deliberately: a setting that could +password manager. In the default background mode a password manager is refused outright, +because the menu bar cannot show what would be typed into your vault; with CMCP_BACKGROUND=0 it asks on every single action, in every mode, including allow. That is not configurable, deliberately: a setting that could turn it off would eventually be turned off. Terminals and editors, where a keystroke can be a command, ask once per app per session.

And an unanswered dialog is a refusal. If you walk away and the agent hits something that @@ -175,13 +214,13 @@

3. You can read back the whole run

first question in the morning, "what did it actually do", has an answer that does not depend on the agent's own account of itself.

-

Setting up a run you can walk away from

+

Setting up a run you can walk away from

  1. Look at your screen first. Close the terminal with the production credentials in the scrollback. Quit the password manager. The redaction handles marked fields; it cannot handle a secret you left sitting in a text window.
  2. Pick the mode on purpose. allow is the default: it keeps going and you read the -log afterwards, and password managers, terminals, quitting and closing still ask. +log afterwards, and terminals, quitting and closing still ask, and password managers are refused. ask adds one dialog before the first write, which then grants the session - so an unattended run in ask mode stalls at its first write until you answer. Neither is wrong; they fail differently, and you should know which failure you chose.
  3. @@ -192,7 +231,7 @@

    Setting up a run you can walk away from

    come back mid-run, and costs far less than a screenshot loop.
-

What it does not protect you from

+

What it does not protect you from

This is the part most pages would leave out. Consent is not containment. Once you have approved the session, the agent is driving your real Mac with your real logged-in sessions. The gate decides whether an action happens; nothing underneath limits what an @@ -205,15 +244,15 @@

What it does not protect you from

When not to use it goes through the rest of that list without softening it.

-

FAQ

-
Can I leave an AI agent running on my Mac overnight?

That is the case this was built for. Password fields are blacked out before any screenshot is written, password managers ask for consent on every single action regardless of mode, terminals and editors once per session, an unanswered dialog is a refusal rather than an approval, and every call lands in a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where you can read back in the morning.

+

FAQ

+
Can I leave an AI agent running on my Mac overnight?

That is the case this was built for. Password fields are blacked out before any screenshot is written, password managers are refused in the default mode (with CMCP_BACKGROUND=0 they ask on every single action), terminals and editors once per session, an unanswered dialog is a refusal rather than an approval, and every call lands in a log the server only appends to - every line carries a fingerprint of itself and the one before it, so a removed or edited line breaks the chain and says where you can read back in the morning.

What happens if a consent dialog appears while I am asleep?

It is refused after CMCP_ASK_TIMEOUT seconds, default 60, and the refusal is written to the log like any other outcome. It never counts as approval, and it does not hold the machine waiting indefinitely.

How do I see what the agent did while I was away?

computer_audit reads back ~/.local/state/computer-mcp/audit.jsonl: one line per call with the tool, the target app, the outcome and the session mark. It is the machine's own record, not the agent's account of itself.

Is my typed text in that log?

Only as a length and a salted SHA-256 prefix. The salt is random per run and never written down. Without that random ingredient, a short password could be worked out offline by whoever ended up with the log, which is exactly why it is salted.

Can I turn off the extra prompts for password managers?

No, and that is deliberate. Apps on the dangerous list ask on every action in every mode, including allow. A setting that could turn it off would eventually be turned off.

Is it safe to leave running?

Safer than the alternatives, and not a sandbox. Consent is not containment: once you approve a session the agent drives your real Mac with no isolation underneath, and prompt injection stays possible. The design makes what happened visible; it does not make anything impossible. For a real blast radius, use a virtual machine.

What should I do before starting a long run?

Look at your own screen. Close terminals with credentials in the scrollback and quit the password manager. The redaction covers what macOS marks as secure; it cannot cover a secret sitting in a plain text window.

-

Related

+
Install for Claude Code All 32 tools What it can and cannot reach diff --git a/docs/use-cases/without-taking-over-your-screen/index.html b/docs/use-cases/without-taking-over-your-screen/index.html index d2d754c6..16544e20 100644 --- a/docs/use-cases/without-taking-over-your-screen/index.html +++ b/docs/use-cases/without-taking-over-your-screen/index.html @@ -17,7 +17,7 @@ - + @@ -133,9 +172,9 @@
-

Computer MCP / Use cases / Without taking over your screen

+

Computer MCP / Use cases / Without taking over your screen

Let an agent work while you keep using your Mac

-

Seven of the thirty-two tools act on a background window without moving your pointer: press an element by its own accessibility action, write into a field, choose a menu item, and - by naming the app - type, press a key, scroll or click into that app's own queue.

+

Seven of the thirty-two tools act on a background window without moving your pointer: press an element by its own accessibility action, write into a field, choose a menu item, and - by naming the app - type, press a key, scroll or click into that app's own queue.

On this page

The usual complaint about computer-use tools is not that they are weak. It is that using @@ -144,7 +183,7 @@

Let an agent work while you keep using your Mac

have to happen.

-

The three that do not touch your pointer

+

The three that do not touch your pointer

@@ -163,7 +202,7 @@

The three that do not touch your pointer

it, such as dragging on a canvas or clicking a spot in a video. Those still need the real pointer, and there is no way around that on macOS.

-

A concrete run

+

A concrete run

Say an export takes four minutes and then a dialog appears. The version that takes over your screen looks like this: activate the app, click, screenshot, screenshot, screenshot, screenshot, click. Every one of those steals focus, and the screenshots cost tokens for @@ -176,7 +215,7 @@

A concrete run

Four calls, no focus change, one image at most. You carry on typing in another app the whole time.

-

What still takes the screen, honestly

+

What still takes the screen, honestly

  • computer_click and computer_type use the real cursor and the real keyboard, because macOS offers no other route for a synthetic event. If the agent @@ -190,7 +229,7 @@

    What still takes the screen, honestly

    the screenshot, because it gets its own Space. Leave the target windowed.
-

Several agents at once

+

Several agents at once

Each MCP client starts its own server process, so running Claude Code and Cursor side by side gives you two independent servers with two independent consent states. They do not queue behind each other. Every line in the audit log carries a per-server session mark, and @@ -200,15 +239,15 @@

Several agents at once

each other's lines. That is not an assumption: there is a test in the repo that runs two servers through fifty interleaved writes and fails if a single line comes out torn.

-

FAQ

+

FAQ

Can an agent use my Mac without taking over the screen?

Largely, yes. computer_press fires an element's own accessibility action rather than simulating a click, so it works on a background window without moving your pointer. computer_set_value writes into a field the same way, and computer_wait_for waits for an element instead of taking screenshots in a loop. What still needs the real screen is anything with no accessibility element behind it, such as dragging on a canvas.

Does clicking a button require bringing the app forward?

Not with computer_press. It triggers the button's own AXPress action where the window stands, behind whatever else is open. computer_click moves the real cursor when you do not name an app. Name one and the event goes into that app's own queue instead - built the same way as typing, and not yet measured for mouse events the way typing has been.

Can I run two agents at the same time?

Yes. Each MCP client starts its own server process with its own consent state, so they do not queue behind each other. Every audit line carries a per-server session mark, and CMCP_CLIENT gives each one a readable name.

Will two servers corrupt the shared audit log?

No. Each write is an only-grows open, and the repo has a test that runs two servers through fifty interleaved writes and fails if a single line comes out torn.

Why does computer_wait_for matter for cost?

Because the alternative is a screenshot loop. Twenty polls through wait_for cost one tool call; twenty screenshots cost twenty images in the model's context, of a progress bar.

The agent says it cannot see a window that is open. Why?

Almost always a full-screen app. True full-screen puts an app on its own Space, and from outside that Space neither the accessibility API nor the screenshot sees the windows underneath. Leave the target app windowed.

-
Does the consent dialog interrupt me every time?

No. In the default allow mode ordinary writes do not ask at all, and everything is logged. Password managers ask every time, regardless of mode; terminals and editors ask once per session; quitting, closing and destructive-looking menu items ask every time. In background mode those questions wait in the menu bar icon instead of popping up. Ask mode adds one dialog before the first write, which then grants the session.

-

Related

+
Does the consent dialog interrupt me every time?

No. In the default allow mode ordinary writes do not ask at all, and everything is logged. Password managers are refused (with CMCP_BACKGROUND=0 they ask every time, regardless of mode); terminals and editors ask once per session; quitting, closing and destructive-looking menu items ask every time. In background mode those questions wait in the menu bar icon instead of popping up. Ask mode adds one dialog before the first write, which then grants the session.

+
Install for Claude Code All 32 tools What it can and cannot reach From eba8bcd83615c4896b4dd816f360d44d5752d787 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:26:45 +0200 Subject: [PATCH 097/214] =?UTF-8?q?side:=20forsiden=20linker=20til=20alle?= =?UTF-8?q?=20undersider=20(=C2=ABGo=20deeper=C2=BB)=20og=20bunden=20f?= =?UTF-8?q?=C3=A5r=20Matrix/Learn/Blog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustav 6/10: «det ser ud til alle undersider ikke er med». Målt: 12 af 22 undersider var ikke linket fra forsiden, og use-cases/without-taking-over-your- screen kunne slet ikke nås fra noget sted. Nu: én sektion efter spørgsmålene med alle 14 guide-, læse-, reference- og blogsider som kort (titel og indledning hentet fra siderne selv, v12/mere.py, gentagelig), og bunden linker til Matrix, Learn og Blog. Crawl fra forsiden: kun 404.html er ikke linket (med vilje). Tjek: tjek-forside ALT GROENT, claims-offline BESTAAET, sync-tal idempotent. Co-Authored-By: Claude Opus 5.5 --- docs/index.html | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/index.html b/docs/index.html index 2027418b..e4ee4bb4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -478,6 +478,13 @@ footer .wrap{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center} footer a{color:var(--muted)} footer .right{margin-left:auto} +/* go deeper v2 */ +.more{margin-top:28px;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px} +.more-card em{font:600 11px/1 var(--mono);font-style:normal;text-transform:uppercase;letter-spacing:.08em;color:var(--blue);margin-bottom:4px} +.more-card{display:flex;flex-direction:column;gap:4px;background:var(--surface);border:1px solid var(--rule);border-radius:12px;padding:14px 16px;color:var(--ink);text-decoration:none} +.more-card b{font-weight:650;line-height:1.3} +.more-card span{color:var(--muted);font-size:14.5px;line-height:1.45} +@media (hover:hover) and (pointer:fine){.more-card:hover{border-color:var(--muted)}} @@ -774,6 +781,29 @@

Questions

+ +
+

Go deeper

+

Guides, the full tool reference and what we measured. Every page below is written against the code it describes.

+
+ Use casesLeaving an AI agent running on your Mac overnightWhat makes a long unattended run survivable: password fields blacked out before the screenshot is written, password… + Use casesLet an agent work while you keep using your MacSeven of the thirty-two tools act on a background window without moving your pointer: press an element by its own… + LearnWhat is a computer-use MCP server?How computer-use MCP servers actually work on macOS - the accessibility tree versus pixels versus a shell, five… + LearnMCP security: what a server can reach, and how to bound itWritten while shipping a computer-use MCP server. Every control below is one we had to implement, and each has a… + LearnThe OWASP MCP Top 10, answered with running codeAll ten OWASP MCP risk categories, and what one macOS computer-use server actually does about each - including the… + ReferenceTool referenceEvery tool Computer MCP exposes: what it does, what it takes, and when to reach for it instead of the obvious one. + ReferenceWhat it can and cannot do"Can an agent do everything a human can on a Mac?" is not a yes or a no. + ReferenceSecurity modelWhat this defends against, how each gate actually works, and - the part most pages skip - what it does not protect… + ReferenceWhen NOT to use Computer MCPIt is not a sandbox, it does not run in CI, it is macOS only, and for anything in a browser there is a better tool. + ReferenceTroubleshootingEvery failure on this page is one we hit ourselves building the thing. + ReferenceUninstalling Computer MCP, and exactly what data it touchesThree steps to remove it, the one file it ever writes, what each macOS permission is for, and the thing that matters… + ReferencePrivacyThis page is short because there is very little to say. Computer MCP has no account, no API key, no telemetry and no… + WritingYour agent's screenshot tool is reading your passwordsIf you have given an AI agent a screenshot tool on the Mac you actually work on, it has almost certainly seen… + WritingDriving macOS from an AI agent: what actually worksFive findings from building a macOS computer-use server. Each one cost real time to learn, and each one looks like a… +
+
+ +

Give it a look before you give it your hands.

@@ -793,6 +823,9 @@

Give it a look before you give it your hands.

Made by Tools Security + Matrix + Learn + Blog Help Privacy Browser MCP From 0bac4b252efac3a0517e868f008c40b4a672c84e Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:41:34 +0200 Subject: [PATCH 098/214] flader: Gemini-beskrivelsen og Windsurf rettet (e2e-gennemgang 6/10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /opensource-gennemgang af hele slut-tilstanden (main + 0.2.1 + siden + npm): - gemini-extension.json lovede «password managers that ask every time»; i standardtilstanden afvises de (policy.js:554-569). - README (kodekommentar) og llms-install.md nævnte stadig Windsurf som klient; den er ikke testet og blev fjernet fra siden 2/10. npm-README afledt igen. Tjek: tjek-forside, claims-offline, sync-tal idempotent, claims 15/15b/42a-d/45 isoleret: OK. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/llms-install.md | 2 +- gemini-extension.json | 2 +- mcp-server/README.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe7fc896..8e130597 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ claude mcp add computer -- npx -y @agent360/computer-mcp ``` ```jsonc -// Cursor, VS Code, Codex CLI, Windsurf - mcp.json +// Cursor, VS Code, Codex CLI - mcp.json { "mcpServers": { "computer": { "command": "npx", "args": ["-y", "@agent360/computer-mcp"] } diff --git a/docs/llms-install.md b/docs/llms-install.md index aa5a2e2a..d2ae28e9 100644 --- a/docs/llms-install.md +++ b/docs/llms-install.md @@ -34,7 +34,7 @@ OpenAI Codex: codex mcp add computer -- npx -y @agent360/computer-mcp@latest ``` -Any other client (Cursor, VS Code, Windsurf, Cline) - write this into that +Any other client (Cursor, VS Code, Cline) - write this into that client's MCP config: ```json diff --git a/gemini-extension.json b/gemini-extension.json index 9fe0b48a..b9365ff7 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,7 +1,7 @@ { "name": "computer-mcp", "version": "0.2.1", - "description": "Drive macOS from any MCP client with the guardrails on by default: password fields blacked out in memory before the screenshot is written, password managers that ask every time and terminals once per session, and a log the server only appends to that never stores typed text in clear. 32 tools, 14 read-only. No API key, MIT.", + "description": "Drive macOS from any MCP client with the guardrails on by default: password fields blacked out in memory before the screenshot is written, password managers that are refused and terminals that ask once per session, and a log the server only appends to that never stores typed text in clear. 32 tools, 14 read-only. No API key, MIT.", "mcpServers": { "computer": { "command": "npx", diff --git a/mcp-server/README.md b/mcp-server/README.md index c950567e..f4b218ca 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -120,7 +120,7 @@ claude mcp add computer -- npx -y @agent360/computer-mcp ``` ```jsonc -// Cursor, VS Code, Codex CLI, Windsurf - mcp.json +// Cursor, VS Code, Codex CLI - mcp.json { "mcpServers": { "computer": { "command": "npx", "args": ["-y", "@agent360/computer-mcp"] } From 02b3d29dea931b3a8f03970a38fbc1dca12fe390 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:42:48 +0200 Subject: [PATCH 099/214] =?UTF-8?q?sikkerhed/tekst:=20README's=20"seven=20?= =?UTF-8?q?others=20ask=20every=20time"=20var=20forkert,=20og=20skal=20nu?= =?UTF-8?q?=20ind=20i=20#6=20F=C3=98R=20npm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustav ændrede rækkefølgen 6/10: npm udgives FØR hjemmesiden merger (for aldrig at vise "npx serves 0.1.0" live). build-release.sh afleder mcp-server/README.md af dette repos README.md ved udgivelse - så enhver tekstfejl i README.md her fryser permanent ind i den udgivne npm-pakke. Den rettelse der allerede var klar i #7 (hjemmesiden, uafhængig Opus-gennemgang 3/10) rettede netop dette: - Password-apps bliver REFUSED i standardtilstanden (baggrund), ikke "ask every time, in every mode" som README sagde - verificeret direkte i policy.js:517-524 (knapErFarlig/viaIkonet): et farligt program kan aldrig godkendes via menulinjen, kun i forgrund (CMCP_BACKGROUND=0). - "Keychain, 1Password and seven others" skulle være "five others" - ni bundle-ID'er, men tre af dem (onepassword, onepassword7, 1password.1password) er samme produkt. Rettet i samme omgang: test/claims.mjs's egen optælling (42b) talte raa bundle-ID'er og "bestod" kun fordi den delte den forkerte antagelse med README'en - cirkulær, beviste intet. - Windsurf/Zed fjernet fra klient-listen (ikke testet). Da npm nu udgives FØR hjemmesiden (fase 2 kommer bagefter), skal denne rettelse ligge i #6/main, ikke kun i #7 - ellers fryser den forkerte tekst ind i npm-pakken for hele 0.2.1's levetid, uopretteligt uden en ny version. Verificeret før commit, ikke blot kopieret: - README.md-diffen er byte-identisk med #7's allerede gennemgåede tekst (git diff mod 6439c6d, ingen forskel). - mcp-server/README.md regenereret med NETOP den formel claims.mjs's check 45 selv håndhæver (afprøvet lokalt: check45 passes: True). - 42a/42b/42c afprøvet isoleret og lokalt uden at røre skærmen (ren fil- og policy.js-læsning, ingen server, intet rigtigt klik) - alle tre bestod. - 42b's rettelse mutationsbevist begge veje: den gamle raa optælling genindsat midlertidigt → 42b fejlede korrekt (andre=7, teksten siger fem) → gendannet → kun den tilsigtede diff tilbage. Ingen funktionskode ændret - kun tekst og en testoptællings formel. Co-Authored-By: Claude Sonnet 5 --- README.md | 28 +++++++++++++++------------- mcp-server/README.md | 28 +++++++++++++++------------- test/claims.mjs | 13 +++++++++++-- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3b7b4ec1..fe7fc896 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A macOS computer-use MCP server built for the part everyone skips: what happens in the hours you are not watching. - **It runs in every MCP client, not one app.** Claude Code, Cursor, VS Code, - Codex, Windsurf, Zed, or your own agent - one `npx` line, the same server + Codex, or your own agent - one `npx` line, the same server everywhere. It is not tied to a single vendor's desktop app or plan. - **It does not take over your Mac.** It presses buttons and fills fields in windows that stay behind the one you are in, and it leaves your pointer where @@ -21,8 +21,8 @@ in the hours you are not watching. - **Several can run at once.** No session lock, no one-at-a-time limit. Writes to the shared log take a short file lock, so two servers cannot break the chain between them. -- **It will not go near your passwords.** Keychain, 1Password and seven others - ask every time, in every mode, and password fields are blacked out while the +- **It will not go near your passwords.** Keychain, 1Password and five others + are refused in the default mode (with `CMCP_BACKGROUND=0` they ask every time), and password fields are blacked out while the screenshot is still in memory. A screen recording leaves password managers out, but does not black out password fields. - **You can read back what it did.** Every call lands in an append-only log, @@ -83,11 +83,12 @@ through - so both are checked. **2. The dangerous places ask first.** By default the agent works without interrupting you - that is what lets it run while you do something else - and every write is logged. What never goes through on its own: password managers and -Keychain ask *every single time*, in every mode, and that one is not -configurable. Terminals and editors, where a keystroke can be a command, ask once +Keychain are refused in the default mode, because the menu bar cannot show what +would be typed (with `CMCP_BACKGROUND=0` they ask *every single time*, in every +mode), and that one is not configurable. Terminals and editors, where a keystroke can be a command, ask once per app per session. Quitting an app, closing a window, switching Space, -destructive-looking menu items and any action whose target app cannot be -identified ask every time. The gate judges the app an action lands in: an +and destructive-looking menu items ask every time; an action whose target app +cannot be identified is refused in the default mode. The gate judges the app an action lands in: an app opened some other way - Spotlight, a shortcut - is guarded by what the agent then tries to do in it. Want a dialog before the first write too? Set `CMCP_MODE=ask`. If nobody answers, the answer is no. @@ -155,8 +156,8 @@ the client and check `computer_permissions` again. | `CMCP_MODE` | Behaviour | |---|---| | `readonly` | Write tools are not even listed. The agent can look and cannot touch. | -| `ask` | The first write opens a dialog; one yes grants the session. Password managers still ask every time, terminals and editors once per session. | -| `allow` | **Default.** Writes proceed without asking, still logged. Password managers *still* ask every time, terminals and editors once per session, and in background mode anything that would need a dialog waits in the menu bar instead. | +| `ask` | The first write opens a dialog; one yes grants the session. Password managers are refused (with `CMCP_BACKGROUND=0` they ask every time), terminals and editors ask once per session. | +| `allow` | **Default.** Writes proceed without asking, still logged. Password managers are *still* refused (with `CMCP_BACKGROUND=0` they ask every time), terminals and editors ask once per session, and in background mode anything that would need a dialog waits in the menu bar instead. | `CMCP_ASK_TIMEOUT` (seconds, default 60) controls how long a dialog waits before it refuses. @@ -167,8 +168,9 @@ it refuses. default, and the agent uses them without asking - the same way a browser tool drives a browser. Two gates survive that, and they are the two that matter: -- **Password managers ask every time.** Keychain, 1Password and seven others, - in every mode, even after you have said yes. That is the whole difference +- **Password managers are refused.** Keychain, 1Password and five others, in + every mode, even after you have said yes; with `CMCP_BACKGROUND=0` they ask + every time instead. That is the whole difference between *you may work* and *you may have my passwords*. - **Anything that deletes or clears asks every time**, recognised from the words in the action itself. @@ -282,9 +284,9 @@ Two or more matches is a refusal, not a guess - the agent gets the candidates and has to narrow it down, because pressing the first plausible button is exactly the kind of almost-right action nobody notices afterwards. -The consent dialog still comes to the front, and the apps on the always-ask list +The consent question still reaches you (in the menu bar by default), and the apps on the always-ask list still ask every time. `computer_press` names its target app, and that name is -what the gate judges - so pressing something in 1Password asks even when +what the gate judges - so pressing something in 1Password is refused (with `CMCP_BACKGROUND=0`: asks) even when 1Password is nowhere near the front. **Claude Code's auto mode and the screen tools.** Measured on 1 Oct: after the diff --git a/mcp-server/README.md b/mcp-server/README.md index 449ed50c..c950567e 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -6,7 +6,7 @@ A macOS computer-use MCP server built for the part everyone skips: what happens in the hours you are not watching. - **It runs in every MCP client, not one app.** Claude Code, Cursor, VS Code, - Codex, Windsurf, Zed, or your own agent - one `npx` line, the same server + Codex, or your own agent - one `npx` line, the same server everywhere. It is not tied to a single vendor's desktop app or plan. - **It does not take over your Mac.** It presses buttons and fills fields in windows that stay behind the one you are in, and it leaves your pointer where @@ -19,8 +19,8 @@ in the hours you are not watching. - **Several can run at once.** No session lock, no one-at-a-time limit. Writes to the shared log take a short file lock, so two servers cannot break the chain between them. -- **It will not go near your passwords.** Keychain, 1Password and seven others - ask every time, in every mode, and password fields are blacked out while the +- **It will not go near your passwords.** Keychain, 1Password and five others + are refused in the default mode (with `CMCP_BACKGROUND=0` they ask every time), and password fields are blacked out while the screenshot is still in memory. A screen recording leaves password managers out, but does not black out password fields. - **You can read back what it did.** Every call lands in an append-only log, @@ -81,11 +81,12 @@ through - so both are checked. **2. The dangerous places ask first.** By default the agent works without interrupting you - that is what lets it run while you do something else - and every write is logged. What never goes through on its own: password managers and -Keychain ask *every single time*, in every mode, and that one is not -configurable. Terminals and editors, where a keystroke can be a command, ask once +Keychain are refused in the default mode, because the menu bar cannot show what +would be typed (with `CMCP_BACKGROUND=0` they ask *every single time*, in every +mode), and that one is not configurable. Terminals and editors, where a keystroke can be a command, ask once per app per session. Quitting an app, closing a window, switching Space, -destructive-looking menu items and any action whose target app cannot be -identified ask every time. The gate judges the app an action lands in: an +and destructive-looking menu items ask every time; an action whose target app +cannot be identified is refused in the default mode. The gate judges the app an action lands in: an app opened some other way - Spotlight, a shortcut - is guarded by what the agent then tries to do in it. Want a dialog before the first write too? Set `CMCP_MODE=ask`. If nobody answers, the answer is no. @@ -153,8 +154,8 @@ the client and check `computer_permissions` again. | `CMCP_MODE` | Behaviour | |---|---| | `readonly` | Write tools are not even listed. The agent can look and cannot touch. | -| `ask` | The first write opens a dialog; one yes grants the session. Password managers still ask every time, terminals and editors once per session. | -| `allow` | **Default.** Writes proceed without asking, still logged. Password managers *still* ask every time, terminals and editors once per session, and in background mode anything that would need a dialog waits in the menu bar instead. | +| `ask` | The first write opens a dialog; one yes grants the session. Password managers are refused (with `CMCP_BACKGROUND=0` they ask every time), terminals and editors ask once per session. | +| `allow` | **Default.** Writes proceed without asking, still logged. Password managers are *still* refused (with `CMCP_BACKGROUND=0` they ask every time), terminals and editors ask once per session, and in background mode anything that would need a dialog waits in the menu bar instead. | `CMCP_ASK_TIMEOUT` (seconds, default 60) controls how long a dialog waits before it refuses. @@ -165,8 +166,9 @@ it refuses. default, and the agent uses them without asking - the same way a browser tool drives a browser. Two gates survive that, and they are the two that matter: -- **Password managers ask every time.** Keychain, 1Password and seven others, - in every mode, even after you have said yes. That is the whole difference +- **Password managers are refused.** Keychain, 1Password and five others, in + every mode, even after you have said yes; with `CMCP_BACKGROUND=0` they ask + every time instead. That is the whole difference between *you may work* and *you may have my passwords*. - **Anything that deletes or clears asks every time**, recognised from the words in the action itself. @@ -280,9 +282,9 @@ Two or more matches is a refusal, not a guess - the agent gets the candidates and has to narrow it down, because pressing the first plausible button is exactly the kind of almost-right action nobody notices afterwards. -The consent dialog still comes to the front, and the apps on the always-ask list +The consent question still reaches you (in the menu bar by default), and the apps on the always-ask list still ask every time. `computer_press` names its target app, and that name is -what the gate judges - so pressing something in 1Password asks even when +what the gate judges - so pressing something in 1Password is refused (with `CMCP_BACKGROUND=0`: asks) even when 1Password is nowhere near the front. **Claude Code's auto mode and the screen tools.** Measured on 1 Oct: after the diff --git a/test/claims.mjs b/test/claims.mjs index 3e275412..15cfa485 100644 --- a/test/claims.mjs +++ b/test/claims.mjs @@ -1824,10 +1824,19 @@ esac check('42a. README udpeger den tilstand koden faktisk starter i', /\*\*Default\.\*\*/.test(raekke), `koden starter i «${standard}»; README-raekken: ${raekke.slice(0, 60) || 'ikke fundet'}`); const ORD = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve']; - const andre = A42.size - 2; // Keychain og 1Password naevnes ved navn + // ⛔ RETTET 6/10 (sidechatten, uafhaengig Opus-gennemgang @77ba8b6): denne linje + // talte RAA bundle-ID'er - men tre af dem (onepassword, onepassword7, + // 1password.1password) er samme produkt i tre version-skikkelser, ikke tre + // programmer. "A42.size - 2" gav 7, og README's "seven others" bestod kun + // fordi begge sider delte den samme fejl - testen beviste ingenting. + // Det rigtige tal er DISTINKTE PRODUKTER: 7 (Keychain, Apple Passwords, + // 1Password, Bitwarden, LastPass, Dashlane, Secretive), minus de 2 navngivne. + const PRODUKT_GRUPPER = [[/^com\.agilebits\.onepassword\d*$|^com\.1password\.1password$/, '1Password']]; + const distinkteProdukter = new Set([...A42].map(b => PRODUKT_GRUPPER.find(([re]) => re.test(b))?.[1] || b)).size; + const andre = distinkteProdukter - 2; // Keychain og 1Password naevnes ved navn check('42b. antallet af adgangskode-programmer i README stemmer med listen', readme.includes(`1Password and ${ORD[andre]} others`), - `listen har ${A42.size}, saa teksten skal sige «${ORD[andre]} others»`); + `listen har ${distinkteProdukter} distinkte produkter (${A42.size} bundle-ID'er), saa teksten skal sige «${ORD[andre]} others»`); const skjult = T42.size - K42.size; const side = fs42.existsSync(join(ROOT, 'docs/index.html')) ? fs42.readFileSync(join(ROOT, 'docs/index.html'), 'utf8') : ''; check('42c. forsidens tal for skjulte vaerktoejer stemmer', !side || side.includes(`adds the ${ORD[skjult]} that do`), From cb8e118c8ffd81fceb233cd8d49af7b14ceb3ade Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:43:59 +0200 Subject: [PATCH 100/214] =?UTF-8?q?tekst:=20serverens=20egen=20AI-instruks?= =?UTF-8?q?=20havde=20samme=20forkerte=20kodeordsp=C3=A5stand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fundet af forbrugeragent-3c (e2e-gennemgang af hele slut-tilstanden 6/10), verificeret selv (grep index.js:202 + syntax-check) før rettelse. VEJLEDNING (teksten modellen selv læser, under "What you will be refused") sagde "they ask every single time, in every mode" - under overskriften "refused", så den modsiger sig selv, og den er forkert af samme grund som README var: i standardtilstanden (baggrund) afvises password-apps, de spørger kun med CMCP_BACKGROUND=0 (policy.js:554-569). Ikke blokerende adfærd - modellen fik allerede en korrekt afvisning med grund - men teksten står i hvert eneste AI-svar fra serveren, og den ligger i selve npm-pakken. Rettet med samme ordlyd som README-rettelsen (02b3d29), for konsistens på tværs af fladerne. Ingen anden forekomst i index.js (grep-bekræftet). Ingen test afhænger af denne tekst (grep-bekræftet). Co-Authored-By: Claude Sonnet 5 --- mcp-server/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mcp-server/index.js b/mcp-server/index.js index f2584d47..79e6b167 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -199,7 +199,8 @@ What you will be refused, and why: - anything that would take the screen while in background mode - the person is working; use the route above instead - the app the person is using right now - wait, or target another app -- password managers and Keychain: they ask every single time, in every mode +- password managers and Keychain: refused in background mode (the default); + with CMCP_BACKGROUND=0 they ask every single time - an unredacted screenshot: that is the person's decision, never the model's - the Computer MCP status icon itself: it is their control surface From 52e55f5f22d902dab60e7d40a7496f7d8b079051 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:48:14 +0200 Subject: [PATCH 101/214] tekst: Windsurf-kommentar og Gemini-beskrivelse havde samme to fejl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fundet af forbrugeragent-3c i deres e2e-gennemgang (0bac4b2 på #7), verificeret ordret mod den commit før anvendelse: - README.md:125 (kodekommentar i mcp.json-eksemplet) nævnte stadig Windsurf som klient - ikke testet, allerede fjernet fra siden 2/10. Står i npm-README (afledt igen her, check 45 bekræftet lokalt). - gemini-extension.json: "password managers that ask every time" var samme forkerte påstand som README/index.js allerede var rettet for - password-apps afvises i standardtilstanden (policy.js:554-569), spørger kun med CMCP_BACKGROUND=0. Denne fil er IKKE del af npm-pakken (ikke i mcp-server/package.json's files-liste, verificeret) - lavere hastværk, men rettet alligevel da ordlyden allerede lå klar. Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- gemini-extension.json | 2 +- mcp-server/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe7fc896..8e130597 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ claude mcp add computer -- npx -y @agent360/computer-mcp ``` ```jsonc -// Cursor, VS Code, Codex CLI, Windsurf - mcp.json +// Cursor, VS Code, Codex CLI - mcp.json { "mcpServers": { "computer": { "command": "npx", "args": ["-y", "@agent360/computer-mcp"] } diff --git a/gemini-extension.json b/gemini-extension.json index 9fe0b48a..b9365ff7 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,7 +1,7 @@ { "name": "computer-mcp", "version": "0.2.1", - "description": "Drive macOS from any MCP client with the guardrails on by default: password fields blacked out in memory before the screenshot is written, password managers that ask every time and terminals once per session, and a log the server only appends to that never stores typed text in clear. 32 tools, 14 read-only. No API key, MIT.", + "description": "Drive macOS from any MCP client with the guardrails on by default: password fields blacked out in memory before the screenshot is written, password managers that are refused and terminals that ask once per session, and a log the server only appends to that never stores typed text in clear. 32 tools, 14 read-only. No API key, MIT.", "mcpServers": { "computer": { "command": "npx", diff --git a/mcp-server/README.md b/mcp-server/README.md index c950567e..f4b218ca 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -120,7 +120,7 @@ claude mcp add computer -- npx -y @agent360/computer-mcp ``` ```jsonc -// Cursor, VS Code, Codex CLI, Windsurf - mcp.json +// Cursor, VS Code, Codex CLI - mcp.json { "mcpServers": { "computer": { "command": "npx", "args": ["-y", "@agent360/computer-mcp"] } From 37922a819ba0dd546e597d78ca0661000b72ac31 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 15:56:23 +0200 Subject: [PATCH 102/214] =?UTF-8?q?WISHLIST:=200.2.2-=C3=B8nske=20-=20menu?= =?UTF-8?q?linje-sp=C3=B8rgsm=C3=A5l=20skal=20kunne=20opdages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustavs eget ord (6/10, videresendt af forbrugeragent-3c): «Det kan du lægge i computermcp todo». Lavstakket, reversibel dokumentations-ændring, ingen kode rørt, ingen npm-pakkefil ramt (WISHLIST.md er ikke i mcp-server/package.json's files-liste). Indholdet er allerede verificeret direkte i denne session: 20 "nobody answered"-udløb i audit.jsonl (15 heraf computer_request_screen), inklusive to udløb mens Gustav aktivt kørte F1-prøven. Samme tal, genmålt i dag af forbrugeragent-3c til 22 af 23 (opdateret siden). Co-Authored-By: Claude Sonnet 5 --- WISHLIST.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/WISHLIST.md b/WISHLIST.md index aebe931d..13006dc8 100644 --- a/WISHLIST.md +++ b/WISHLIST.md @@ -23,6 +23,21 @@ Nothing from outside yet - the package is two days old. The forms above are the --- +## ⏳ Waiting for 0.2.2 + +- **A menu-bar question has to be noticeable, not just askable.** Measured 6/10 in the audit log: + 23 questions went through the icon, 1 was approved. 22 timed out (15 of them screen-lending), + while a person was sitting at the machine. Screen-lending already has a 5-minute window, measured + separately - the window is not the problem, the question itself is too easy to miss (a colour + change in the menu bar, no sound, no notification). In practice "deletion and sending asks you + first" becomes "refused quietly", and screen-lending cannot be used at all. Proposed: a real + macOS notification (`UNUserNotificationCenter` from `cmcp-status`) naming the app and the action, + with Allow/Deny buttons; a short sound per question; the icon blinking until it is answered; and + a test asserting exactly one notification per question. Measure after shipping: the share of + approved-or-denied versus timed-out menu-bar questions in `audit.jsonl`. + +--- + ## ⏳ Waiting for 0.3.0 - **A running server never tells you it is out of date.** Measured 20/9, and it cost a real day: From c114153fb7c2ea39f09949ed41f796c7f4574b0a Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Tue, 6 Oct 2026 17:46:19 +0200 Subject: [PATCH 103/214] =?UTF-8?q?grafik:=20de=20tre=20gamle=20m=C3=B8rke?= =?UTF-8?q?=20billeder=20udskiftet=20i=20det=20nye=20design=20og=20sande?= =?UTF-8?q?=20for=200.2.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gustav 6/10: «er alt website og front facing graphic done?» - nej, tre stod tilbage: - docs/demo-poster.jpg (README'ens topbillede på GitHub og npm, og forsidens film- plakat): ny plakat med de tre porte (sikkert felt malet sort i hukommelsen, menulinje-ikonet der bliver orange og spørger, loglinje med længde + saltet fingeraftryk + kaede). Teksterne er fra cmcp-status (main.swift:437,446,645,692); kodeordsprogrammer står som afvist, ikke spurgt. - docs/og-image.jpg (delingskortet for 20 undersider): nyt kort i forsidens design; de 6 og:image:alt-tekster der beskrev det gamle billede er skrevet om. - docs/demo.mp4 fjernet: den gamle mørke animation viste en dialog midt på skærmen, som 0.2.1 ikke har (spørgsmål går til menulinjen). Forsidens afsnit viser nu plakaten og linker til security.html. Kilde: v13/porte.py i plans (HTML -> headless Chrome, dobbelt opløsning). Tjek: tjek-forside ALT GROENT, claims-offline BESTAAET, sync-tal idempotent, 126 billedreferencer / 0 mangler. Co-Authored-By: Claude Opus 5.5 --- docs/blog/driving-macos-from-an-ai-agent.html | 2 +- docs/blog/index.html | 2 +- docs/blog/screenshot-password-leak.html | 2 +- docs/demo-poster.jpg | Bin 108835 -> 148382 bytes docs/demo.mp4 | Bin 2429726 -> 0 bytes docs/index.html | 13 +++++-------- docs/og-image.jpg | Bin 47977 -> 53373 bytes docs/security.html | 2 +- docs/tools.html | 2 +- docs/troubleshooting.html | 2 +- 10 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 docs/demo.mp4 diff --git a/docs/blog/driving-macos-from-an-ai-agent.html b/docs/blog/driving-macos-from-an-ai-agent.html index d11a4121..1258972e 100644 --- a/docs/blog/driving-macos-from-an-ai-agent.html +++ b/docs/blog/driving-macos-from-an-ai-agent.html @@ -16,7 +16,7 @@ - + diff --git a/docs/blog/index.html b/docs/blog/index.html index 8c7e9935..69837564 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -20,7 +20,7 @@ - +
ToolWhat it does instead
computer_press