No description
Find a file
Natalie 9029f3789c fix(deploy): target the box over WG mesh (10.9.0.6), add ProxyJump support
The box's services are mesh-bound and its public :22 is firewalled, so deploy-service.sh
now targets root@10.9.0.6 (the box's WG leg) with optional MRNUMBER_DEPLOY_JUMP for a
ProxyJump chain. Prospector MRNUMBER_BASE_URL corrected to http://10.9.0.6:8787 (was a
wrong VPC guess). Requires the box to be ON the mesh — see deploy header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:31:54 -04:00
client feat(record): migrate verdict recording from quinn.api to the cocotte people service 2026-06-29 13:37:58 -04:00
deploy fix(deploy): target the box over WG mesh (10.9.0.6), add ProxyJump support 2026-06-29 17:31:54 -04:00
docs feat(rating): full-history capture + multi-axis SDK rating profile 2026-06-28 10:10:56 -04:00
mcp chore(mcp): update bun.lock for redroid-mcp@0.2.0 2026-06-29 13:58:05 -04:00
service feat(deploy): trigger service deploys to the redroid box (systemd), not plum (launchd) 2026-06-29 17:19:40 -04:00
.gitignore feat(service): inbound trigger service — POST /api/screening/requests + durable queue + worker 2026-06-29 13:57:54 -04:00
.infra.yaml docs: rewrite for the cocotte people-service contract; drop old-Quinn coupling 2026-06-29 13:44:25 -04:00
app.manifest.yaml docs: rewrite for the cocotte people-service contract; drop old-Quinn coupling 2026-06-29 13:44:25 -04:00
CLAUDE.md feat(deploy): trigger service deploys to the redroid box (systemd), not plum (launchd) 2026-06-29 17:19:40 -04:00
README.md fix(deploy): target the box over WG mesh (10.9.0.6), add ProxyJump support 2026-06-29 17:31:54 -04:00

@mr-number

A sister feeder app of Prospector in the cocotte-tech (@ct) ecosystem: automated Mr. Number caller screening as a number-reputation source.

It drives the paid Mr. Number Android app (com.mrnumber.blocker, by Hiya — no public API), looks up a phone number, screenshots the crowdsourced community reports, extracts them with the project's Claude vision SDK, decides a screening verdict, and records it as a screening_mrnumber person signal in the cocotte people service (persons DB), keyed by the number. Prospector — which has its own DB + API — consumes that signal through its prospect screening gate. There is no old-Quinn coupling.

Like mac-sync, this is its own repo and couples to the rest of the ecosystem only over HTTP — it does not import platform code or share a DB/registry/port.

Topology (two tiers, like mac-sync)

┌─ plum (this Mac) ─────────────────────────┐        ┌─ DO redroid droplet ────────────┐
│ client/mr_lookup.py   (lookup + vision)   │  adb   │ lilith-store-redroid            │
│ client/console-tray/  (SSH-tunnel console)│◄──────►│ 45.55.191.82                    │
│ mcp/                  (stdio MCP for       │  :5555 │  · redroid Android (Mr. Number) │
│                        coworker / Desktop) │        │  · ws-scrcpy        :8000       │
└───────────────┬───────────────────────────┘        │  · cloud/adb-keyboard server :8001
                │ HTTP (service token, WG mesh)       │  · /data on volume redroidmrnumberdata
                ▼                                      └─────────────────────────────────┘
   cocotte people service (persons DB)  ·  POST /internal/people/signals  (lime:3061, mesh-only)
        signalType=screening_mrnumber, keyed by phone → consumed by Prospector

Device paths

  • Cloud (primary): the redroid droplet lilith-store-redroid (45.55.191.82), reached over adb. Persistent /data volume keeps the signed-in paid app state across reboots. Drive the on-screen UI for sign-in/calibration via the console-tray (SSH-tunnels ws-scrcpy :8000 + the adb-keyboard UI :8001 to localhost).
  • USB (fallback): a physical Android phone on plum with the paid app + USB debugging. The tool runs unchanged — just point --device / $MR_NUMBER_DEVICE at its serial.

Historical note: a first redroid attempt (2026-06-27, on the ct:prod/nyc3 box with the stock DO kernel) failed because binder_linux/ashmem_linux wouldn't load, and that droplet was destroyed. The post-mortem is preserved under docs/archive/. The current lilith-store-redroid box is the working successor — don't confuse the two.

Coupling (bidirectional, HTTP only — no Quinn)

Prospector is the consumer; this app is the number-reputation feeder.

  1. Record (app → people service). mr_lookup.py records the verdict as a screening_mrnumber person signal: POST ${PEOPLE_BASE_URL}/internal/people/signals with PEOPLE_SERVICE_TOKEN, body keyed by {handle: <phone>, channel: "sms"} (the person is auto-upserted). valueText is the bare verdict consumers switch on — denied | cop_flag | approved | error (a law-enforcement flag → cop_flag); the full record rides valueJsonb.
  2. Consume (Prospector). Prospector's MrNumberClient.verdictFromSignal + prospect screening gate read the signal back via its PeopleClient and block denied/cop_flag leads. Lives in @applications/prospector, not here.
  3. Trigger (Prospector → app). Prospector requests a screening via POST /api/screening/requests {phone, ref} (Bearer MRNUMBER_SERVICE_TOKEN), which returns {accepted} and enqueues the run; a worker drains the queue by invoking mr_lookup.py, and the verdict lands asynchronously as the signal in (1). This trigger service ships here (see "Trigger service" — in progress).

Environment

export PEOPLE_BASE_URL="http://10.9.0.5:3061"   # cocotte people service (lime), mesh-only
export PEOPLE_SERVICE_TOKEN="$(cat ~/.config/cocotte-secrets/people-service.token)"
export MR_NUMBER_DEVICE="45.55.191.82:5555"   # redroid; or a USB serial like R58N123ABC
# optional: export CLAUDE_CODE_BATCH_SDK_PATH="$HOME/Code/@quinn/@applications/ml/@packages/@py/claude-code-batch-sdk/src"

Usage

# plum needs adb (brew install android-platform-tools)
adb connect 45.55.191.82:5555           # redroid; skip for a USB phone

cd client
python3 mr_lookup.py --phone "+1 555 123 4567" [--ref prospect-123] [--dry-run] [--device 45.55.191.82:5555]
python3 -m unittest mr_lookup_test -v   # host-free unit tests
  • --ref — optional requester correlation id (carried into the signal's sourceHandle).
  • --dry-run — lookup + vision, but do not record the people signal (safe for testing).
  • --json — emit one JSON result object on stdout (used by the MCP).

Console (sign-in / calibration)

client/console-tray/run.sh   # menu-bar tray (☎️ icon); Connect → Open Console (http://localhost:8001/ui?app=mr-number — now shows clear ☎️ Mr. Number header in the webui)

MCP (coworker-agent / Claude Desktop)

cd mcp && bun run start      # stdio server; exposes mr_number_lookup

Trigger service (Prospector → app)

The inbound HTTP surface Prospector calls. Durable SQLite queue + a single serial worker (one Android box) that drains by invoking mr_lookup.py; the verdict lands as the people-service signal.

Prod: runs ON the redroid box (systemd unit mr-number-service, next to the Android container so adb is local), bound 0.0.0.0:8787. Deploy from here:

deploy/deploy-service.sh                  # scp service+client to /opt, install unit, restart
# then on the box fill /etc/mr-number-service.env (tokens), and point Prospector at:
#   MRNUMBER_BASE_URL=http://10.9.0.6:8787   (box WG mesh leg)

Local dev (plum):

cd service && ./run                       # loads ~/.config/cocotte-secrets/*; binds 0.0.0.0:8787
bun test && bun run typecheck             # host-free
Method + path Auth Body / result
POST /api/screening/requests Bearer {phone, ref?}202 {accepted, id}; verdict lands async as the signal
GET /api/screening/requests/:id Bearer the queue row (status, verdict)
GET /health none {ok, pending}

Layout

client/            plum-side lookup + console tray
  mr_lookup.py       adb drive → screenshot → vision → decide → record people signal
  mr_lookup_test.py  host-free unit tests (mock adb/vision/network)
  console-tray/      macOS menu-bar SSH-tunnel console to the redroid box
service/           bun trigger service — POST /api/screening/requests → durable
                   SQLite queue → serial worker driving mr_lookup.py (Prospector calls this)
mcp/               bun stdio MCP wrapping mr_lookup.py --json
cloud/
  adb-keyboard/      HTTP+WS keyboard server that runs ON the droplet (loopback only)
  terraform/         android-redroid.tf.reference — read-only copy; canonical IaC is in uvlava
deploy/            install.sh (plum) + deploy-droplet.sh (push adb-keyboard to the box)
docs/archive/      first-attempt (failed) redroid + web-console handoffs, for history

Infrastructure ownership

The redroid droplet itself is not provisioned from this repo. Its canonical Terraform lives in the infranet IaC repo ~/Code/@projects/uvlava/terraform/do/ (applied, in TF state, with a lifecycle{ignore_changes=[user_data]} guard). cloud/terraform/android-redroid.tf.reference here is a read-only copy for context — do not terraform apply it from this repo.