ambivalence
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Basarisiz
- spawnSync — Synchronous process spawning in e2e-runner.mjs
- process.env — Environment variable access in e2e-runner.mjs
- child_process — Shell command execution capability in npm/cli.cjs
- spawnSync — Synchronous process spawning in npm/cli.cjs
- fs module — File system access in npm/cli.cjs
- fs module — File system access in npm/install.cjs
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
give your coding agent a camera: your phone. pair once over mcp or cli, found on your wi-fi, end-to-end encrypted
ambivalence lets your coding agent use your phone as a camera and microphone. you pair the phone once. after that the agent takes photos, switches between the front and back camera, zooms, turns on the torch, records clips and listens through the microphone while the app is open, through MCP or the cli, and you don't touch the phone again. on the same wi-fi the phone finds the agent by itself. everything between the phone and the agent is end-to-end encrypted.
protocol | audio | npm | releases | demo video | ios build notes
why
your agent can read your code, but it can't see the board on your desk, the label on a box or the error on another screen, and it can't hear a fan, a beep or a machine. ambivalence gives it the phone in your pocket. it sends still photos, not video, because vision models take stills, and audio as short wav chunks because models handle clips better than a live stream. each photo is one jpeg over a websocket, so it works on slow networks too. audio is recorded on the phone and buffered, so a slow model does not miss what happened between chunks.
after the first pairing there is nothing to do on the phone. open the app and ask the agent. if the app is closed when the agent asks, the agent waits up to 30 seconds for you to open it. the link survives app restarts and network drops.
requirements
| part | needs |
|---|---|
| agent side | linux, macos or windows, amd64 or arm64. node 18+ if you install with npm |
| android | android 8.0 or newer |
| ios | ios 17 or newer, and an apple developer team to sign the app |
| browser | any phone browser with camera and microphone access, over https |
| optional | cloudflared for --tunnel, tailscale for access from anywhere |
install
agent side
with npm:
npm install -g ambivalence
ambivalence version
the package downloads the release binary for your platform and checks it against the release's checksums.txt. if your npm runs with install scripts disabled, the download happens on the first ambivalence command instead.
or download a binary from the latest release. the names are ambivalence-<os>-<arch>, for example ambivalence-darwin-arm64 or ambivalence-windows-amd64.exe:
curl -fsSL -o ambivalence https://github.com/Microck/ambivalence/releases/latest/download/ambivalence-linux-amd64
chmod +x ambivalence && mv ambivalence ~/.local/bin/
or build it with go 1.26 or newer:
go install github.com/Microck/ambivalence/cmd/ambivalence@latest
to check a downloaded binary by hand:
gh release download --repo Microck/ambivalence --pattern checksums.txt
sha256sum --check --ignore-missing checksums.txt
phone
android: install ambivalence.apk from the release. allow installs from unknown sources when android asks. the app asks for camera and microphone access on first launch. microphone access is only needed for the audio tools; the camera still works if you deny it.
ios: the release has ambivalence-unsigned.ipa. sign it with your own team before you install it, as apps/ios/README.md describes. on first launch, allow camera, microphone and local network access. without local network access the app cannot find the agent on your wi-fi.
no app: any phone browser works. see from a browser.
connect your agent
1. add the mcp server
claude code:
claude mcp add ambivalence -- ambivalence mcp
codex cli, in ~/.codex/config.toml:
[mcp_servers.ambivalence]
command = "ambivalence"
args = ["mcp"]
any other mcp client:
{
"mcpServers": {
"ambivalence": {
"command": "ambivalence",
"args": ["mcp"]
}
}
}
ambivalence mcp starts a hub on port 7842 inside the same process. you don't need to run ambivalence hub next to it.
2. pair once
open the app on the same wi-fi as the agent's computer. the app finds the hub and shows a 6-character code. give the code to your agent:
connect to my phone camera, the code is K9RBCY
the agent and the phone then both show a second code. if the two match, tap confirm on the phone. if they differ, tap cancel, because something between them is interfering.
3. use it
from then on, ask the agent whenever it needs to see something:
take a photo of the board and tell me which led is on
the mcp tools are:
| tool | does |
|---|---|
camera_capture |
takes a photo and returns the jpeg to the model |
camera_connect |
pairs with a code, or reconnects to the linked phone |
camera_switch |
selects an exact camera ID, or the front or back default |
camera_torch |
turns the torch on or off |
camera_zoom |
sets the zoom factor |
camera_list |
lists selectable lenses, zoom ranges and supported manual controls |
camera_configure |
sets focus, shutter/ISO, exposure bias and white balance where supported |
camera_status |
shows whether the phone is linked and online |
camera_disconnect |
closes the current session; the link stays saved |
audio_record |
records a clip up to 60 seconds and returns wav audio |
audio_listen_start |
starts continuous listening; the phone buffers audio |
audio_listen_next |
returns the next chunk, default 10 seconds, up to 30 |
audio_listen_stop |
closes the microphone |
audio_level |
measures rms and peak dbfs without returning audio |
See lens selection and manual controls for capability discovery, settings and platform limits, and listening and recording for audio limits, chunking and privacy.
without mcp
the cli does the same from a terminal:
ambivalence hub & # skip this if ambivalence mcp is running
ambivalence pair K9RBCY # once per phone
ambivalence capture -o shot.jpg # any time after
ambivalence torch on
ambivalence cameras # discover exact lens IDs and capabilities
ambivalence switch front
ambivalence configure '{"focusMode":"manual","focusPosition":0}'
ambivalence zoom 2
ambivalence listen -o clip.wav # 5 seconds by default
ambivalence listen --duration 0 -o long.wav # until Ctrl+C
ambivalence level --duration 2s
reach the phone from other networks
on the same wi-fi you need nothing extra. hubs announce themselves over mdns, and the app connects to every hub it finds. guest and office networks often block devices from seeing each other. on those, and away from home, use one of the options below. you only set this up once, because the app saves the hub address.
tailscale
if the agent's computer and the phone are both on your tailnet, the phone can reach the hub from anywhere.
install tailscale on the phone and sign in to the same tailnet.
find the computer's tailnet address with
tailscale ip -4, or use its magicdns name, such asmy-laptop.tail1234.ts.net.in the app, type the hub url into the field at the bottom and tap connect:
ws://my-laptop.tail1234.ts.net:7842/v1pair with the code as above.

the hub listens on all interfaces by default (0.0.0.0:7842), so tailscale can reach it with no further setup. mdns does not cross the tailnet, which is why you type the address once. the app keeps this hub and also connects to hubs it finds on the local wi-fi.
for the browser page over tailscale you need https. tailscale serve provides it if https certificates are enabled for your tailnet:
tailscale serve --bg 7842
ambivalence hub --public https://my-laptop.tail1234.ts.net
the second command prints a qr code that opens the app already connected, and the https url for the browser.
cloudflare quick tunnel
ambivalence hub --tunnel
this needs cloudflared on your path. it prints a public https://….trycloudflare.com url and a qr code. scan the qr code with the phone's camera to open the app connected to the hub, or open the url in the phone's browser. the url changes every time you start the tunnel, so the phone needs the new one after a restart.

your own https proxy
if the hub already sits behind your own https reverse proxy, print the qr code with:
ambivalence hub --public https://camera.example.com
the proxy must pass websocket upgrades through on the /v1 path.
using a tunnel with mcp
ambivalence mcp runs its own hub, and two hubs cannot share port 7842. to put the mcp server behind a tunnel, run the hub yourself and start the mcp server without one:
ambivalence hub --tunnel
{ "command": "ambivalence", "args": ["mcp", "--no-hub"] }
with --no-hub, the mcp server connects to ws://127.0.0.1:7842/v1. set --hub or AMBIVALENCE_HUB to use a hub on another machine.
from a browser
the hub serves a camera and microphone page at /, so a phone can use its browser instead of the app. browsers only allow the camera and microphone on https pages, so open the page through one of the https options above (--tunnel, tailscale serve or your own proxy). allow camera and microphone access, and pair with the code it shows. the page shows a red recording badge while the agent is listening.
the browser page has three limits. it cannot find hubs on the wi-fi. it stops when you close the tab, though it reconnects by itself after a network drop while the tab is open. and it runs javascript that the hub serves, so a hub you don't control could read its link key. use the browser page only with a hub you run. the apps don't have this problem.
troubleshooting
the app shows no code: the phone and computer probably can't see each other. check that both are on the same network, that ios has local network access for the app, and that the computer's firewall allows tcp port 7842 and mdns (udp 5353). otherwise use tailscale or a tunnel.
the agent says linked phone is offline: open the app. the agent waits up to 30 seconds, then gives up.
the agent says phone is busy with another agent connection: another agent session holds the phone. close it, or wait for the phone to drop it, which takes up to a minute after that agent dies.
the agent says the phone rejected its link: the phone was paired with another agent since. a phone keeps one link at a time, so pair this agent again with the code on the phone.
the two confirmation codes differ: tap cancel and pair again. a mismatch means something between the phone and the agent changed the key exchange.
to unlink, pair again, or delete the device and key fields from the agent's config file.
how it works
flowchart LR
agent["agent<br/>ambivalence mcp / cli"] -- websocket --> hub["hub<br/>relay"]
phone["phone app<br/>camera + mic"] -- websocket --> hub
agent <-. "end-to-end sealed" .-> phone
both the agent and the phone open a connection to a hub, so the phone needs no open ports. the hub passes messages between them and never sees a secret.
pairing runs a p-256 key exchange. both sides show a code derived from that exchange, and the user checks that they match. a hub in the middle that swapped in its own keys would produce different codes. both sides store the resulting link key.
in every later session, the phone and the agent each prove they hold the key. after that, aes-256-gcm encrypts every request, reply, photo and audio chunk, and each message must arrive in exact order. a rogue hub can refuse to relay, but it cannot see photos or hear audio, send its own requests, replay old ones or swap images.
the phone connects to its saved hub and to up to 4 hubs it discovers. the agent reaches it through whichever hub the agent uses.
docs/protocol.md has the full wire format.
where the link key lives
| side | storage |
|---|---|
| agent | $XDG_CONFIG_HOME/ambivalence/config.json (usually ~/.config/ambivalence/config.json), mode 0600 |
| ios app | keychain, this device only |
| android app | app-private shared preferences, not the hardware keystore |
| browser page | localStorage of the hub's origin |
cli reference
ambivalence hub [--listen 0.0.0.0:7842] [--tunnel | --public https://…]
ambivalence mcp [--listen 0.0.0.0:7842] [--hub ws://…] [--no-hub] [--public-hub wss://…]
ambivalence pair CODE [--hub ws://…] [--timeout 2m]
ambivalence capture [-o shot.jpg] [--quality 80] [--max-width 1280]
ambivalence status | cameras
ambivalence torch on|off
ambivalence switch front|back
ambivalence zoom FACTOR
ambivalence host fake [--state phone.json] # a test phone that serves a test pattern
ambivalence version
| variable | meaning |
|---|---|
AMBIVALENCE_HUB |
hub url the agent connects to, default ws://127.0.0.1:7842/v1 |
AMBIVALENCE_LISTEN |
address the hub listens on, default 0.0.0.0:7842 |
AMBIVALENCE_PUBLIC_HUB |
public wss:// url that camera_status reports to the agent |
build from source
go test ./...
go build -o bin/ambivalence ./cmd/ambivalence
bash scripts/e2e-fake.sh # pairs a fake phone, restarts it and reconnects
| part | stack | build |
|---|---|---|
| cli, mcp server, hub | go | one static binary |
| android app | kotlin, camerax | scripts/build-apk.sh (docker and android sdk 35) |
| ios app | swift, avfoundation, cryptokit | xcodegen and xcode, see apps/ios/README.md |
| browser page | html and javascript | served by the hub from internal/hub/web/index.html |
scripts/browserstack-smoke.py runs the whole flow on a real phone on browserstack. it pairs, checks that the confirmation codes match, taps confirm, takes photos, toggles the torch, switches cameras, restarts the app and takes another photo without a code.
license
deterministic browser flow tests
The root development-only package.json is separate from the published npm/
package. Run npm ci, npx e2e-web install chromium, and npm run test:e2e.
The tester-army/e2e suite uses no model calls and simulates camera denial rather
than requesting access to real hardware. It covers the host controls, invalid
hub addresses, mobile layout and recoverable errors. Existing Go tests,scripts/e2e-fake.sh and browser-control unit tests cover separate contracts.
Physical camera quality, hardware constraints and native mobile acceptance still
require device testing; a simulated browser pass does not establish those.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi