
> commands
> node bin/list-commands.mjs --md

# bluebut command catalog

> AUTOGENERATED — do not hand-edit. Regenerate: `npm run commands -- --md > docs/COMMANDS.md`.
> Source of truth: `adapters/facebook/*.js` + `messenger/jobs/*.js` (the `cli({})`) crossed with `adapters/_lib/taxonomy.js` OPS.

**51 commands** — 34 **WIRE** (GraphQL/Socket replay, the doctrine) · 17 **dom\*** (legacy DOM scrapers = migration backlog, do NOT copy). `✎`=write (mutates FB/DB), `·`=read.

Run any of them as a job (cloakbox-only — the runner resolves the account via the
cloak-cdp HTTP API `cdp_load` → a public gateway drivable from any box, no tunnel):
```bash
  node bin/bluebut-adapter.mjs adapters/facebook/<cmd>.js --account <slug> [args]
```
Or from the UI/bridge: `{ site, cmd }` → auto-mapped (`facebook`→`adapters/facebook/<cmd>.js`, `messenger`→`messenger/jobs/<cmd>.js`).

## facebook — `adapters/facebook/`

| | cmd | op | source | args | what |
|---|---|---|---|---|---|
| · | `sync-group-geo` | `(unregistered)` | dom\* | `dry-run?` `limit?` `refresh-polygons?` | Assign coverage polygons to our FB groups (fb_geo_area + fb_group_geo_area) so listings match groups |
| ✎ | `accept-admin-invite` | `admin.accept_invite` | **WIRE** | `account?` `group?` `decline?` `role?` | Loaded account accepts (or --decline declines) a pending group admin/moderator invite via the Accept |
| ✎ | `update-group-admins` | `admin.role` | **WIRE** | `account?` `group?` `promote?` `demote?` `remove?` `role?` | Invite an existing group member to admin/moderator/expert role (or remove them). Promotion sends a p |
| · | `check-doc-ids` | `diag.doc_ids` | **WIRE** | `account?` `friendly?` `surfaces?` | Discover the CURRENT doc_id for GraphQL FriendlyNames from FB's served JS bundles (page-is-truth-sou |
| ✎ | `accept-friend-requests` | `friend.accept` | **WIRE** | `account?` `between-seconds?` `max-accept?` | Accept ALL pending incoming Facebook friend requests off the wire (FriendingCometFriendRequestConfir |
| ✎ | `add-friend` | `friend.add` | **WIRE** | `account?` `profile?` `group?` | Send a Facebook friend request off the wire (FriendingCometFriendRequestSendMutation). --group scope |
| · | `list-friends` | `friend.list` | **WIRE** | `account?` | Wire-based inventory of every accepted Facebook friend of the loaded account (reads the inlined prof |
| · | `list-friend-requests` | `friend.list_requests` | **WIRE** | `account?` | Wire-based list of pending friend requests — incoming (inlined) + outgoing (persisted-query replay), |
| ✎ | `approve-join-request` | `group.approve_join` | **WIRE** | `account?` `group?` `user?` `decline?` | Admin approves (or --decline) a pending group join request off the wire (GroupsCometApprove/DeclineP |
| ✎ | `create-group` | `group.create` | **WIRE** | `account?` `name?` `privacy?` | Create a new Facebook group off the wire (useGroupsCometCreateMutation) — the account becomes admin. |
| ✎ | `update-group-cover` | `group.edit_cover` | **WIRE** | `account?` `group?` `url?` `force?` | Set a Facebook group's cover photo from a public image URL via FB's group-cover upload endpoint (wir |
| ✎ | `update-group-description` | `group.edit_description` | **WIRE** | `account?` `group?` `description?` `name?` `force?` | Update a Facebook group's description (and optionally its name) via the useGroupEditNameAndDescripti |
| ✎ | `update-group-location` | `group.edit_location` | **WIRE** | `account?` `group?` `location?` `replace?` `force?` | Set a Facebook group's location(s) by resolving a place name and replaying useGroupEditLocationsMuta |
| ✎ | `update-group-setting` | `group.edit_setting` | **WIRE** | `account?` `group?` `setting?` `value?` `name?` `dry-run?` | Flip any group admin setting (post/member approval, privacy, discoverability, private replies, name/ |
| · | `get-group-feed` | `group.feed` | **WIRE** | `account?` `group?` `groups?` `all?` `max-groups?` `stalest?` | Wire replacement for scrape-group: read one or MANY groups' posts off the persisted feed GraphQL (Gr |
| ✎ | `get-group` | `group.get` | dom\* | `account?` `group?` `persist?` | Get full info for one Facebook group — name, member count, privacy, my role, cover image, and all ad |
| ✎ | `ingest-groups` | `group.ingest` | **WIRE** | `account?` `max?` `pages?` `prune?` `force-prune?` | Scrape an account\'s groups + admins and commit each to public.fb_* on bluebut-db as we go |
| · | `get-group-insights` | `group.insights` | **WIRE** | `account?` `group?` `days?` | Read a group's admin Insights (Growth) — members joined, posts+comments, views (total + trend delta  |
| ✎ | `invite-to-group` | `group.invite` | **WIRE** | `account?` `group?` `friends?` `names?` `max?` | Invite Facebook friends to a group off the wire (useGroupsCometInviteLinkCreateWithNotificationBulkM |
| ✎ | `join-group` | `group.join` | **WIRE** | `account?` `group?` | Loaded account joins (or requests to join) a Facebook group via the GroupCometJoinForumMutation wire |
| · | `get-group-join-config` | `group.join_config` | **WIRE** | `account?` `group?` `persist?` | Read a Facebook group's join configuration — participation questions (+ options), group rules, rules |
| · | `get-user-groups` | `group.list` | **WIRE** | `account?` `pages?` `persist?` `fast?` | The complete wire-based picture of an account's groups: viewer identity (user/page), every joined gr |
| · | `list-joined-groups` | `group.list_joined` | **WIRE** | `account?` `pages?` | Enumerate every group the account has joined off the persisted GraphQL the 'Your groups · joined' ta |
| ✎ | `create-group-post` | `group.post` | **WIRE** | `account?` `group?` `title?` `text?` `share-post?` `image-url?` | Publish a post (text + optional image + optional hashtags) to a Facebook group as the loaded account |
| ✎ | `delete-group-post` | `group.post_delete` | **WIRE** | `account?` `post-url?` `post?` `group?` `story-id?` `force?` | Delete a group post WE authored (wire — persisted-mutation replay) |
| ✎ | `scrape-new-members` | `group.scrape_admin` | **WIRE** | `account?` `group?` `limit?` `lookback-days?` | Read a group\'s join/participation-change activity off the persisted-GraphQL wire (admin only). Capt |
| · | `search-groups` | `group.search` | **WIRE** | `account?` `query?` `pages?` `category?` `max?` `persist?` | Discover groups by keyword off the persisted GraphQL the /search/groups/ page reads (SearchCometResu |
| ✎ | `sync-new-groups` | `group.sync_new` | **WIRE** | `account?` `max?` `pages?` | Like ingest-groups but only probes + commits groups NOT already stored for the account — fast increm |
| ✎ | `classify-posts` | `lead.classify` | dom\* | `limit?` `group?` `post-url?` `redo?` | Categorize scraped fb_post rows via one LLM call each — writes post_extract_listings (offers) or pos |
| ✎ | `queue-outreach` | `outreach.queue` | dom\* | `limit?` `direction?` `source?` `dry?` | Build the outreach queue: match candidate leads (posts + member-requests) against the reach policy,  |
| ✎ | `load-page-session` | `page.connect` | dom\* | `account?` `managing-account?` `page-id?` | Load a Facebook Page session: log in the managing profile, switch into the Page, verify the switch t |
| · | `get-page-insights` | `pages.insights` | **WIRE** | `account?` `page?` `time-range?` | Fetch the figures from Meta Business Suite Insights → Results for one Page (impressions, reach, inte |
| ✎ | `list-pages` | `pages.list` | **WIRE** | `account?` `no-db?` | Enumerate every Facebook Page a profile manages off the inlined Relay wire (profile_switcher_eligibl |
| ✎ | `get-activity-log` | `person.activity_log` | **WIRE** | `account?` `category?` `id?` `pages?` `persist?` | Extract an account's Facebook Activity Log for a category (posts/comments/reactions/group-posts/sear |
| ✎ | `scrape-profile` | `person.scrape` | dom\* | `account?` `fb-id?` `persist?` | Read a profile\'s FULL object off the inlined Relay wire (Shape B) — identity block + all public abo |
| · | `get-post-comments` | `post.comments` | **WIRE** | `account?` `post?` `group?` `persist?` `cap?` | Read a group post\'s comments off the inlined Relay wire (Shape B) → fb_comment (author, body, depth |
| ✎ | `load-session` | `session.connect` | dom\* | `account?` `wipe-first?` `auto-reauth?` | Load a stored Facebook session (inject cookies + verify live status) into a dedicated persistent tab |
| ✎ | `login` | `session.login` | **WIRE** | `account?` `hold?` `force-creds?` | Log in / re-auth an FB account ON THE BOX (drive the login form on the account\'s anti-detect Chromi |
| ✎ | `set-account-language` | `session.set_language` | **WIRE** | `account?` `language?` | Force an account's Facebook UI language preference via the language-picker GraphQL mutation (default |
| · | `whoami` | `session.whoami` | dom\* | `account?` | Report which Facebook account the loaded chromebox session is currently authenticated as (fb_id, nam |

## messenger — `messenger/jobs/`

| | cmd | op | source | args | what |
|---|---|---|---|---|---|
| ✎ | `send-message` | `dm.send` | dom\* | `account?` `to?` `text?` `abort-on-prior?` `transport?` | Send a Messenger DM off the wire (mautrix-exact SendMessageTask over the LightSpeed socket, via the  |
| ✎ | `e2ee-connect` | `messenger.e2ee_connect` | dom\* | `account?` `reset?` | STAGE 4 (write, registers an E2EE device on first run → fires the new-device alert): extract the CAT |
| · | `e2ee-probe` | `messenger.e2ee_probe` | dom\* | `account?` | STAGE 0 (read-only, registers nothing): confirm the E2EE device-registration inputs (fbid, app_id, e |
| · | `e2ee-status` | `messenger.e2ee_status` | dom\* | `account?` | STAGE 4 (read): report the bluebut-e2ee daemon\'s live E2EE-device state for an account (connected,  |
| · | `fetch-older` | `messenger.fetch_older` | dom\* | `account?` `thread?` `pages?` `max-messages?` `before-ts?` | Page a Messenger thread's OLDER history off the LightSpeed wire (via the bluebut-e2ee daemon) → fb_m |
| · | `listen` | `messenger.listen` | dom\* | `account?` `duration-sec?` `warm-sync?` | Hold the Messenger LightSpeed MQTT-over-WS socket open and decode inbound message pushes in realtime |
| · | `ls-start` | `messenger.ls_start` | dom\* | `account?` | Start/refresh the account's PERSISTENT LightSpeed session in the daemon (browser-sync replacement: i |
| ✎ | `manage-thread` | `messenger.manage` | dom\* | `account?` `thread?` `action?` | Manage a Messenger conversation (mark-read implemented; archive/delete/mute/accept-request gated unt |
| · | `sync-inbox` | `messenger.sync_inbox` | **WIRE** | `account?` `folders?` `wait-ms?` | Read the whole Messenger mailbox (all folders incl. message-requests/spam/archived) off FB\'s LightS |
| · | `sync-thread` | `messenger.sync_thread` | **WIRE** | `account?` `thread?` `rounds?` `wait-ms?` | Read one Messenger conversation's full message history off the LightSpeed wire → fb_message/fb_messa |
| ✎ | `outreach-send` | `outreach.send` | dom\* | `limit?` `channel?` `dry?` | Drain approved outreach rows and send each by channel (dm via JIT weighted-pool sender + send-messag |

## passive wires — `adapters/_lib/passive-wire.js`

Not commands — the ambient harvester (`harvestEntities`) runs these on EVERY job, decoding whatever wire traffic already flowed. `harvest` = typename-routed entity upsert; `freshen` = self-scoped to the running account.

| kind | wire | routes on / writes |
|---|---|---|
| harvest (`__typename`-routed) | `person` | `User` |
| harvest (`__typename`-routed) | `group` | `Group` |
| harvest (`__typename`-routed) | `page` | `Page` |
| harvest (`__typename`-routed) | `post` | `Story` |
| harvest (`__typename`-routed) | `comment` | `Comment` |
| harvest (`__typename`-routed) | `group_search` | `SearchProfileViewModel` |
| harvest (`__typename`-routed) | `managed-page` | `ProfileSwitcherEligibleProfile` |
| signature (`__typename`-less) | `group-about-fragment` | fb_group (description/location/created_time/member_count — merged into the `group` wire by id) |
| signature (`__typename`-less) | `join-config` | fb_group flags + fb_group_join_question + fb_group_rule (the join contract; additive) |
| signature (`__typename`-less) | `invite` | fb_group_invite (group invites off the notifications-jewel tracking blob) |
| signature (`__typename`-less) | `notification` | fb_notification (ALL jewel notifs — type/actor/target/body — off the tracking blob; per-account) |
| signature (`__typename`-less) | `my-membership-edge` | fb_account_group is_member + fb_group_member OUR role (admin only via if_viewer_is_admin gate; never guesses member) |
| freshen (self-scoped) | `own-identity` | keyvault (acting fb_id/type/name off the doc) |
| freshen (self-scoped) | `own-avatar` | keyvault + fb-media (avatar bytes when the token changed) |
| freshen (self-scoped) | `own-status` | keyvault (last_status=logged_in when FB served an authed doc) |
| freshen (self-scoped) | `own-session` | keyvault (persist the fresh jar when logged_in) |
| freshen (self-scoped) | `managed-pages` | keyvault (managed_pages — pages this account can act AS, so a keyvault-only loader like fb-bot can act as them) |

