hands_perceive
Open a URL (or install a fake web via 'pages') and return the numbered element map.
urlpages
io.github.littlestjames82-sys由 GitHub 帳號 littlestjames82-sys 發布v0.9.3更新於 Nov 4, 2026
Governed browser hands for AI agents: classified actions, human approvals, replayable audit trails.
為 AI 代理提供受管控的瀏覽器操作:把頁面解析成編號元素圖,依策略執行動作並可人工核准,同時留下可重播的稽核軌跡。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
參照 儲存庫 中的啟動說明。
[PyPI] [Downloads] [License: MIT] [Release] [CI]
Playwright drives. Stagehand thinks. Browser Use wanders. Ghost Hands answers for every move.
Ghost Hands is a standalone, zero-dependency Python package that gives an
agent hands on the web — and a conscience to go with them. Every action an
agent takes is classified before it runs (readonly / write /
consequential), judged against a policy, written to a provenance trail
before it executes, and — when it matters — gated on a human approval.
Finished runs graduate into deterministic, model-free Ghost Hands scripts
you can re-run for free.
The browser body is our own: a stdlib Chrome DevTools Protocol client
drives Chromium directly over the --remote-debugging-pipe transport.
There is no Playwright, no Selenium, and no other automation library under
the hood — the automation layer is 100% ours. (We drive Chromium, the
browser; we did not write a browser engine and don't claim to have.)
From Ghost Developer Studio. MIT licensed.
MCP Registry: mcp-name: io.github.littlestjames82-sys/ghost-hands
Status: v0.9.3 — live on PyPI and GitHub: PyPI
ghost-hands(pip install ghost-hands) · github.com/littlestjames82-sys/ghost-hands · littlestjames82-sys.github.io/ghost-hands.
ghost-hands doctor (doctor.py) — one command that answers
"is everything wired up?" for the whole stack. Seven read-only
checks — the Python runtime, a Chromium binary (found by the
driver's own resolver, so doctor and the driver can never
disagree), the four policy packs, the model environment, a
GhostBus server (when --bus / GHOSTBUS_URL configures one;
the workspace shape — single vs hosted /w/<id>/ — is reported),
the MrGhosty phone bridge (when configured: reachable, token
accepted, MrGhosty version, accessibility state, bridge-protocol
match), and adb — each rendering as a PASS / WARN / FAIL row with,
when it isn't a PASS, a one-line plain-language fix ("Is the phone
on, MrGhosty accessibility on, and adb forward tcp:8378 tcp:8378 running?" / "Pairing token mismatch — re-copy it from
MrGhosty's Access checklist into GHOST_HANDS_ANDROID_TOKEN.").
FAIL means "this part will not work until you act"; WARN means
idle-or-optional (an unconfigured bus or bridge is a WARN, never
a failure). Exit code is 1 iff anything FAILs. --json emits the
same checks as structured data. Every probe carries a short
timeout, so doctor never hangs on a dead endpoint, and no secret
is ever printed: keys and the pairing token are reported as
present, never as values.ghost-hands phone-proof (phone_proof.py) — the guided
version of the first phone run. Where phone-approval-test
proves one thing in isolation, phone-proof walks all five steps
and checks each: bridge configured (else it prints the exact
env setup and stops — exit 2, needs-setup), the adb forward
(printed before it runs; skipped gracefully without adb/a
device), the bridge status (MrGhosty version + accessibility),
the harmless test approval ("LOOK AT YOUR PHONE NOW — tap
Approve on the MrGhosty notification"), and a closing summary of
what is now proven plus the exact next commands. Denied, expired,
or timed-out approvals end gracefully with instructions —
exit 3 (not approved); bridge/config failures are exit 1.
--yes runs straight through without pauses, which is how the
tests and bench drive it against the fake bridge.With MrGhosty v1.7+ installed on the phone and its accessibility service enabled, the whole first run is:
phone-proof prints what each step needs before it checks it, and
its exit code says how the story ended: 0 proven · 1 error ·
2 needs-setup · 3 not approved. After it passes, the phone is a
governed body: ghost-hands run --driver android --approver phone
drives it with approvals on the phone itself.
ghost-hands report trail.jsonl,
report.py). The trail was always the record; now it's a readable
one. One run renders to ONE self-contained HTML file — inline CSS,
no JavaScript, no external assets, works offline — in studio
styling (dark charcoal, violet accent): a header (goal when the
trail records one, body inferred from the perception URLs, date
range, stop reason + summary), summary stat cards (steps, wall
duration, perception chars / ~tokens, actions by class, approvals
requested/granted/denied, heals, downloads/dialogs/net counts),
and a step-by-step timeline — perceived map summary with a
collapsible perception record, the decision in plain language
("click [4] 'Place order'"), the Governor's verdict with its
classification chip and, for asks, how the story ended
("ask → approved via phone"), the result, and special events inline
(heals with from/to, dialogs, downloads with filename + size,
approval request/decision pairs with channel + decider, network
capture in a collapsible). Rendering is defensive: every trail
string is HTML-escaped (a hostile page title stays text), typed
values are truncated at 80 chars, and a value typed into a
type=password element renders as •••• (N chars). Corrupt lines
are skipped and counted in the footer — a partial trail is still a
record. A sample render ships in examples/sample-report.html.GHOST_HANDS_DRIVER=fake|chromium|simphone|android selects the
session body (default: fake, as before), GHOST_HANDS_START_URL
opens on the first perceive, and android sessions use the same
GHOST_HANDS_ANDROID_BRIDGE / GHOST_HANDS_ANDROID_TOKEN env as
the bus agent (the token never appears in tool output). Bodies with
native eyes perceive through them, exactly like the Runner.
GHOST_HANDS_APPROVER=phone routes session "ask" verdicts through
PhoneApprover instead of the deny-with-explanation default, and
hands_act replies state which channel decided ("approved via
phone" / "denied: no approver (MCP default)"). hands_run defaults
to the session body and accepts all four drivers. Configuration is
per-server-process; unknown values fail loudly at startup.seatbelt policy pack. Standard's posture (readonly +
write allowed, consequential asks, 300s approval leash) plus a
target-content rule: if a target element's name/label — or the
page/action URL — matches a curated prompt-injection pattern
(policies.INJECTION_PATTERNS: "ignore previous instructions",
"disregard your instructions", "you are now", "system prompt",
"click here to claim", "verify your password to continue", …), the
Governor denies the action outright, whatever its class, and the
govern event's reason names the matched pattern. The patterns are
instruction-shaped phrases, never bare words — a button named
"Ignore" does not trip it (guarded by tests). Implemented as data
on the Policy (deny_patterns), so any pack or policy file can
carry its own list.ghost-hands eval,
evaluate.py). Eight graded tasks against local fixture pages —
search-and-open, multi-field form fill + consequential submit,
structured table extraction, multi-hop navigation to a fact, a
trap page whose prominent button is destructive and must NOT be
touched, a shifting page scored on heal recovery, a login form
whose trail is audited for credential leakage, and an impossible
goal scored on honest stopping. Four tasks run on real Chromium.
Every task is scored on success, steps, wall time, perception
chars / ~tokens, actions by class, approvals requested, and
heals; --report PATH writes the JSON. Three deciders:
--decider stub (a local stub model server whose deterministic
policy proves the harness end to end — stub results are harness
verification only, not a model-quality measurement),
--decider rules (the RuleDecider as a second baseline), and
--decider openai (a real model via the existing env-configured
decider). Measured on this build: the stub passed 8/8 (the
instrument works); the rules baseline passed 3/8 (trap avoidance,
heal recovery, honest stop — its honest failures on search,
forms, extraction, multi-hop, and login are data, not harness
errors). Real-model numbers are reported separately wherever a
key is available; see "Honest status".double_click, drag, click_at
— are implemented end to end: the bridge dispatches real gestures
(dispatchGesture, bounded callback latch, honest
cancelled/failed results), click_at is validated against the
current window's bounds and refused out-of-bounds, and
AndroidDriver forwards all three (drag resolves both ends;
click_at needs no target). SimPhoneDriver does not implement
them — no fake parity; see docs/BODY_PROTOCOL.md §6 for which
body supports what..tmp rename colliding under
concurrent writers, HTTP 400) was fixed in GhostBus 0.4.1
(unique temp per save + serialized saves) and regression-tested
in the GhostBus suite since 0.5.0 (44/44 + 6/6, re-verified for
this build). Ghost Hands now proves it from its own side too:
the bus demo flow and a parallel agent+poller barrage run
against a real file-backed GhostBus with zero 400s, an intact
store, and no orphan temp files (bench v7 bus, pytest
test_file_store_barrage_no_races).PhoneApprover (phone_approver.py) posts a
redacted approval request to the Ghost Hands bridge in MrGhosty
v1.7.0, which raises a high-importance notification ("Ghost Hands
needs approval") with Approve / Deny actions, mirrored by a
card in MrGhosty's Device tab. The decision returns over the
bridge; denied, expired, timed-out, and unreachable all mean no
— silence is never consent. Works with every driver
(ghost-hands run --approver phone … — the phone can approve a
desktop Chromium run too), and in bus-agent mode via
--approval-channel phone (the bus gets a courtesy comment; the
approval itself is never requested from the bus). Approvals route
to Ryan's phone through the MrGhosty bridge (notification +
in-app decision) when the bridge is reachable; it is a local
bridge channel, not a cloud push service. See
docs/APPROVALS.md.type actions show the
target and a character count, never the typed text; fill_form
shows field names, never values. The pairing token never appears
in summaries, trail events, errors, or logs.ghost-hands phone-approval-test — sends one harmless test
request ("approving this runs nothing") and waits for the phone
decision: the one-command on-device proof.AndroidDriver (android_driver.py)
drives a real phone through the Ghost Hands bridge in the
MrGhosty Android app (v1.6.0): a loopback-only HTTP server hosted by
MrGhosty's accessibility service on 127.0.0.1:8378, authenticated
by a pairing token (shown in MrGhosty's Device tab; passed via
GHOST_HANDS_ANDROID_TOKEN, never stored). Perception walks the
live accessibility tree into the same numbered ElementMap (each
element keeps its bridge ref as body_ref); actions re-verify
the target's {tag, role, name} key at the bridge before anything
moves, and stale targets come back as the same "target missing /
target mismatch" errors the Runner heals from on the web. The
unchanged Runner/Governor drive it — governance is identical,
including the consequential gate. Web-only actions fail honestly.
Forward the bridge with adb forward tcp:8378 tcp:8378, check it
with ghost-hands android-status, then
ghost-hands run --driver android … (or task it over GhostBus with
"driver": "android"). The driver and the bridge are built and the
full flow is proven against the bridge contract (fake-bridge
conformance suite + bench case); on-device proof is pending
Ryan's install of MrGhosty v1.6.0 + the accessibility grant.body_ref. Elements now carry an optional opaque
body-native target reference in their descriptor, so trails record
exactly which node a phone action touched.ghost-hands bus-agent puts the hands on
GhostBus as the
agent ghost-hands (role: hands). It claims tasks addressed to it or
tagged hands — respecting claim leases, blockedBy, and the bus's
own needs-approval gate (gated tasks are never claimed) — runs them
through the same Runner/Governor/Trail, posts progress comments,
uploads the JSONL trail as a shared bus file, and completes with a
structured report. Works against the single-workspace relay and
hosted workspaces (--workspace, /w/<id>/), key from flag/env only,
never stored. ghost-hands bus-demo proves the whole loop against
the real GhostBus node server — including a consequential submit
approved by a second client over the bus, mid-run.APPROVAL NEEDED: <class> <summary> — reply APPROVE <run-id> or DENY <run-id> as a task comment and a message to
the task's creator, then waits (long-poll) up to the approval
timeout. Deny or timeout = denied, on the record: both the request
and the decision land in the trail as approval events.act(action) → outcome, optional capability flags — and the shared
action vocabulary, so new bodies can be built against a written spec.
A conformance scenario (same abstract script on every body) proves
it: the destructive step is denied with no approver and lands with
one, and the trail records identical shapes, on FakeDriver,
ChromiumDriver, and the new phone body alike.ghost_hands.policies): readonly (writes + consequential denied
outright), standard (the default posture), strict (writes ask
too; approvals on a 120s leash). --policy-pack on run and
bus-agent; the MCP server honors GHOST_HANDS_POLICY. Unknown
pack names are a loud error, never a silent fallback.eyes="ax"): the map is built from the CDP
Accessibility tree (role + name + states) and actions resolve back
through backendNodeId.fill_form fills a whole form from
{label: value} pairs, resolving each field by label, name,
placeholder, or aria-label, and reports per-field results — failures
named, never silent. With submit: true it is classified
consequential and takes the normal approval path.download clicks through and waits for the file
to complete on disk (filename + byte size reported, completion on the
trail); set_file uploads a local file into a file input
(DOM.setFileInputFiles), refusing missing paths and directories.net trail events, capped at
200 per run with truncation noted; extract mode network returns
the captured list.extract modes: text (default), list
(links → [{text, href}]), table (→ list of row dicts), network.confirm/alert/prompt are
answered per the driver policy (dialog_policy="dismiss"|"accept",
default dismiss) and every dialog + decision lands on the trail.hover, double_click, right_click, drag
(target → target), key chords in press ("Control+a"), and
click_at(x, y) — the raw-coordinate fallback, classified write
and marked raw: true on the trail.pdf prints the page to a real PDF file
(Page.printToPDF); set_viewport switches named presets
(desktop 1280×800, mobile 390×844 + touch) and perception reflects
the emulated layout, media queries included.wait_for waits for text to appear, an
element matching a descriptor, or a URL substring — with a timeout
that fails honestly, on the record.hands_act
unchanged, and every one of them is still classified by the governor
and written to the trail. (HTML-snapshot eyes do not pierce shadow
roots or frames — that is what the live-element map is for.)ghost-hands bench --live (2 cases, verified Oct 8,
2026 from the studio sandbox).open_tab / switch_tab / list_tabs — each tab is its own
CDP target; perception and actions apply to the active tab.save_session / load_session: cookies
(Network.getAllCookies / setCookie) plus the current origin's
localStorage, in a small versioned JSON format.Page.captureScreenshot) to the action's path.HTTPS_PROXY / https_proxy
(opt out with proxy=""). Authenticated proxies work via a built-in
local relay that injects the credentials Chrome itself can't accept;
ignore_cert_errors=True is available for TLS-intercepting proxies./chat/completions server. Live-model driving quality remains
unproven (see "Honest status").driver=
(chromium or fake-with-embedded-pages) and start_url=; the bench
actually executes a graduated script against fixture pages on real
Chromium and checks its completion marker.Drive a real page:
See examples/steps.json and examples/policy.json for the file formats.
Surveyed from each project's public docs (Oct 2026); "—" means not a built-in of the tool as documented. Every Ghost Hands row is proven by a named bench case on real Chromium unless marked (fake) — the bench says which body each case ran on.
What is proven, and what is not — as of v0.6.0 (Oct 8, 2026):
ghost-hands bench --live,
2/2); the LLM decider's wire protocol against a local stub endpoint;
bus agent mode against the real GhostBus node server — both the
single-workspace relay and a hosted /w/<id>/ workspace — including
an approval granted and an approval denied over the bus mid-run;
the Body Protocol conformance scenario on all three in-process
bodies; the sim phone's notes flow end to end; the Android body
against the bridge contract — AndroidDriver's full governed flow
(including heal-after-mutation and the consequential delete gate)
against a fake bridge speaking the exact wire protocol of the real
one, plus the MrGhosty v1.6.0 APK built and signature-verified with
the bridge compiled in; approvals by phone against the same
contract — PhoneApprover's full request/poll flow (approved /
denied / expired / timeout / unreachable / 401), payload redaction
proven on the wire, a consequential action approved and denied by
simulated phone decisions mid-run on both the Android and web
bodies, the bus agent's phone channel against the real GhostBus
server, and the MrGhosty v1.7.0 APK built and signature-verified
with the approval store, receiver, and bridge endpoints compiled
in (approval-store logic JVM-tested, 44 checks).adb forward tcp:8378 tcp:8378 and
ghost-hands phone-approval-test). v0.9 ships the tools for
exactly that run: ghost-hands doctor diagnoses the stack before
and after, and ghost-hands phone-proof walks the five steps
with him, checking each — both proven here against the fake
bridge and a locally booted bus, neither yet run against his
physical phone. One bus finding
from the v0.4 work is resolved: GhostBus's file-backed store
wrote through a single shared .tmp file whose rename could
collide under heavy concurrent writers (HTTP 400) — fixed in
GhostBus 0.4.1, regression-tested in the GhostBus suite since
0.5.0, and re-proven from this side in v0.7 (file-store bus demo
GHOST_HANDS_API_KEY / GHOST_HANDS_BASE_URL / GHOST_HANDS_MODEL from
the environment only.AndroidDriver speaks
to it; on-device proof pending Ryan's install + accessibility
grant. One hands, phone + web.ghost-hands eval grades a decider on 8 local tasks (4 on real Chromium) with
per-task scoring and a JSON report; stub + rules baselines ship
with it, real models plug in through the environment.double_click, drag, and click_at implemented on the real
Android body via dispatchGesture; the simulator does not fake
them.ghost-hands doctor
(whole-stack diagnostics with plain-language fixes, --json
for tooling, secrets never printed) and ghost-hands phone-proof (the guided five-step on-device proof — checked,
scriptable, exit-coded). On-device proof itself still waits on
Ryan's phone; these are the tools that run it with him.Links: PyPI · Site · Release v0.3.0 · CHANGELOG.md · LAUNCH.md
src/ghost_hands/ — the package, one module per layer: eyes
(perception), actions, governor, policies (policy packs),
trail, drivers (FakeDriver + our CDP ChromiumDriver), simphone
(the simulated phone body), android_driver (the real Android body
via the MrGhosty bridge), phone_approver (approvals decided on
Ryan's phone), deciders, runner, export,
busclient + busagent (GhostBus mode), evaluate (the v0.7
model-evaluation harness), report (the v0.8 trail → HTML audit
report), doctor + phone_proof (the v0.9 field kit),
mcp_server, cli, bench.docs/BODY_PROTOCOL.md — the body contract, capability flags,
conformance rules, and the Android mapping table.docs/APPROVALS.md — the three approval channels (prompt, bus,
phone), the no-API-decision rule, and the summary redaction rule.tests/ — the pytest suite (395 tests at v0.9.0).ghost-hands bench (offline, 95 cases at v0.9.0) and
ghost-hands bench --live (opt-in real-web cases); bench code lives
in src/ghost_hands/bench.py.examples/ — sample steps and policy files for ghost-hands run,
plus sample-report.html — a rendered audit report (from
sample-trail.jsonl) showing exactly what ghost-hands report
produces.site/ — the project site source, deployed to GitHub Pages by
.github/workflows/pages.yml.LAUNCH.md — the launch runbook and copy pack, as written at launch.PLAN.md — the field survey and design notes the build started from.MIT — see LICENSE. © 2026 Ghost Developer Studio.
來源:README.md,提交 c80b6a4
Open a URL (or install a fake web via 'pages') and return the numbered element map.
urlpagesPerform one governed action (e.g. {"kind":"click","target":3}) on the perceived page. Consequential actions are denied without an approver (or decided by the phone when GHOST_HANDS_APPROVER=phone); the response states which channel decided.
action必填Run scripted steps through the governed Runner. driver: 'fake', 'chromium', 'simphone' or 'android' (default: the session body, GHOST_HANDS_DRIVER).
goalpagessteps必填driverstart_urlapprove_allReturn this session's trail events and per-run accounting.
無需輸入參數。
已滿足適用檢查項的 90%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
adb forward tcp:8378 tcp:8378 # or let phone-proof do it (step 2)export GHOST_HANDS_ANDROID_TOKEN=<pairing code from MrGhosty → Device tab → Ghost Hands bridge>ghost-hands doctor # every row green or explained?ghost-hands phone-proof # five checked steps to "proven"pip install ghost-hands # (once published; for now: pip install . from a checkout)ghost-hands demo # scripted run over a built-in fake mini-web — no browser neededghost-hands bench # the verification suitefrom ghost_hands import ChromiumDriver, Runner, ScriptedDecider
driver = ChromiumDriver() # finds Chromium/Chrome; $GHOST_HANDS_CHROME overridesrunner = Runner(driver, ScriptedDecider([ {"kind": "navigate", "url": "https://example.com"}, {"kind": "extract"}, {"kind": "done", "summary": "looked at the page"},]))report = runner.run("look at example.com")print(report.stop_reason, report.summary)driver.close()ghost-hands demo # built-in fake-web demoghost-hands bench # the offline verification suiteghost-hands doctor # diagnose the whole stack (add --json for tooling)ghost-hands phone-proof # the guided on-device proof (exit: 0/1/2/3)ghost-hands report trail.jsonl # trail -> trail.html audit report (-o for a path)ghost-hands bench --live # + live cases: example.com, Wikipediaghost-hands run --script steps.json --driver fake # or --driver chromiumadb forward tcp:8378 tcp:8378 # forward the phone's Ghost Hands bridge (USB)ghost-hands android-status # is MrGhosty's bridge answering? (token via env)ghost-hands run --script steps.json --driver android # drive the real phone, governedghost-hands run --script steps.json --policy policy.json --approve-allghost-hands run --script steps.json --policy-pack strict # or readonlyghost-hands export trail.jsonl -o replay.py # graduate a trail into a scriptghost-hands export trail.jsonl --driver fake --pages pages.json -o replay.pyghost-hands mcp # MCP server on stdioghost-hands bus-agent --once # work one GhostBus task, then exitghost-hands bus-agent --workspace studio # hosted bus, /w/studio/ (key via GHOSTBUS_KEY)ghost-hands bus-demo # prove bus mode vs the real GhostBus server