Petos Ble Gateway — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Petos Ble Gateway (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
PetOS BLE Gateway is a local Node.js bridge for controlling an ESP32-C3 PetOS watch over BLE.
It exposes three control surfaces over the same BLE connection:
Web UI / HTTP API / MCP tool
|
v
Node.js gateway
|
v
BLE JSON write
|
v
PetOS-C3 watchThe current firmware advertises as PetOS-C3 and accepts JSON writes on:
7f2a0001-4f6d-45f6-b805-2b0a7a0f9c017f2a0002-4f6d-45f6-b805-2b0a7a0f9c01The tested target is an ESP32-C3 round watch board with a 240x240 GC9A01 display.
PetOS-C3This project uses @abandonware/noble for BLE access. On macOS, the terminal or app running Node may need Bluetooth permission in System Settings.
npm installnpm startOpen:
http://127.0.0.1:8787The page lets you scan, connect, send named actions, send a fixed frame, update the watch text page, or write raw JSON. It can also pack Codex pets into .idxrle packages, list saved packages with a preview frame, and upload a saved package to the watch over BLE.
Scan:
npm run scan -- 7000Send an action:
npm run send -- '{"cmd":"pet.action","value":"review"}'Show a fixed frame:
npm run send -- '{"cmd":"pet.frame","value":12}'Upload a pet RLE package:
npm run upload -- /absolute/path/to/pet.idxrlePack a Codex pet into a watch-ready RLE package:
npm run pack -- cloud-strifePack and upload in one command:
npm run pack-upload -- cloud-strifeThe packer uses the standard Codex pet 8x9 sprite sheet layout, removes empty cells, resizes frames to 200x200, defaults to 24 colors, and excludes run_right / run_left for the watch package. Packed packages are saved under packed/<pet>/. Running the same pet/settings again reuses the matching .idxrle; add --force to rebuild:
npm run pack -- cloud-strife --forcePlay an action:
{"cmd":"pet.action","value":"idle"}{"cmd":"pet.action","value":"review"}Show one fixed frame:
{"cmd":"pet.frame","value":12}Show multiline text on the second watch page:
{"cmd":"watch.text","title":"Market","text":"#22c55e CPO +2.3%#\n#f97316 NVDA +1.1%#"}The watch firmware supports LVGL label recolor syntax for simple rich text:
#22c55e green text# #f97316 orange text#This is not HTML or Markdown. It supports multiline text and color spans, but not mixed font sizes, bold text, or embedded images.
Supported actions in the current firmware:
idlewavingjumpingfailedwaitingrunningreviewUpload a new pet package:
{"cmd":"rle.begin","size":561600}Then send binary BLE chunks:
"RLEC" + uint32_le(offset) + raw bytesFinish:
{"cmd":"rle.end"}The firmware overwrites /pet.idxrle directly. During upload the watch hides pet frames and shows a progress bar. After rle.end, the gateway waits for the watch notification:
{"event":"rle.complete","a":41,"b":778786}The web/CLI upload should only be treated as successful after this watch-side acknowledgement. If the upload is interrupted, upload the .idxrle again.
Status:
curl http://127.0.0.1:8787/api/statusScan:
curl -X POST http://127.0.0.1:8787/api/scan \
-H 'content-type: application/json' \
-d '{"timeoutMs":7000}'Connect:
curl -X POST http://127.0.0.1:8787/api/connect \
-H 'content-type: application/json' \
-d '{}'Play an action:
curl -X POST http://127.0.0.1:8787/api/action/review \
-H 'content-type: application/json' \
-d '{}'Send raw JSON:
curl -X POST http://127.0.0.1:8787/api/send \
-H 'content-type: application/json' \
-d '{"cmd":"pet.frame","value":12}'Update the second watch page text:
curl -X POST http://127.0.0.1:8787/api/watch/text \
-H 'content-type: application/json' \
-d '{"title":"Market","text":"#22c55e CPO +2.3%#\n#f97316 NVDA +1.1%#"}'Upload an .idxrle file:
curl -X POST 'http://127.0.0.1:8787/api/rle/upload?chunkSize=160&delayMs=10' \
-H 'content-type: application/octet-stream' \
--data-binary @/absolute/path/to/pet.idxrleList saved packages:
curl http://127.0.0.1:8787/api/rle/packagesUpload a saved package from packed/:
curl -X POST http://127.0.0.1:8787/api/rle/upload-file \
-H 'content-type: application/json' \
-d '{"file":"cloud-strife/cloud-strife_watch-no-lr_200_24.idxrle","chunkSize":160,"delayMs":10}'Pack a Codex pet:
curl -X POST http://127.0.0.1:8787/api/rle/pack \
-H 'content-type: application/json' \
-d '{"name":"cloud-strife","colors":24,"size":200}'Pack and upload:
curl -X POST http://127.0.0.1:8787/api/rle/pack-upload \
-H 'content-type: application/json' \
-d '{"name":"cloud-strife","colors":24,"size":200,"chunkSize":160,"delayMs":10}'Start the gateway first:
npm startThen run the MCP server:
npm run mcpFor an MCP client, use this command:
node /absolute/path/to/petos-ble-gateway/src/mcp-server.jsAvailable MCP tools:
petos_statuspetos_scanpetos_connectpetos_send_jsonpetos_play_actionpetos_show_framepetos_show_textpetos_upload_rlepetos_pack_rlepetos_pack_upload_petThe MCP server calls the local gateway at http://127.0.0.1:8787 by default. Override with:
PETOS_GATEWAY_URL=http://127.0.0.1:8787 npm run mcpIf the device does not appear in macOS Bluetooth settings, use this gateway or a BLE scanner such as nRF Connect/LightBlue. Ordinary BLE GATT devices often do not appear like keyboards or headphones.
If scanning hangs or returns no devices:
PetOS-C3.npm run scan -- 10000.If action buttons appear to do nothing, check the log panel. A successful send looks like:
sent {"cmd":"pet.action","value":"review"}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.