Reference: every command, MCP tool and hook
Generated from the code by bun run docs:build, so it always matches what log does. The README covers why and how to start; this is the full list.
Commands, MCP tools and hooks
log-cli is platform-agnostic: 7 platforms so far, Facebook (23 commands of its own), X (18 commands of its own), Reddit (1 command of its own), Discord (8 commands of its own), LinkedIn (2 commands of its own), Slack (9 commands of its own), Instagram (6 commands of its own), and a new one is made by log new platform <name> (rule 83). A command that acts on one platform names it first, log <platform> <command> … (log facebook listings, log twitter session, log reddit session, log discord session, log linkedin session, log slack session, log instagram session), in every form; one that works on any platform takes none (the Platform column, rule 63).
Each command runs on the platform's direct API first where it has one, and on the account's browser otherwise (the Engine and Browser columns). log-cli is browser-agnostic too: each account's browser is a driver (AdsPower first, Camoufox kept, more browser providers to come), and every driver opens through the same gates.
Every command is a CLI command (log [<platform>] <command>) and, unless it only works in a terminal, an MCP tool on the log server (Facebook's take the command's name, every other platform's start with the platform: twitterReply, redditSessionCheck).
The same hooks fire whether it runs from a shell or over MCP, and both write the same live log, ~/.logCli/mcp/<day>.log.
This section and docs/diagrams/*.mmd are generated from the code (COMMANDS, AGENT_HOOKS, PAGE_STATES, the saved flows) by bun run docs:build.
bun run check:docs (lefthook pre-commit and pre-push) refuses a commit where they differ (rule 56).
The platforms
log-cli is platform-agnostic: each platform is a row in PLATFORMS and PLATFORM_SITES (src/cli/runner/helpers/platforms.ts), with its direct-API client from DIRECT_ROUTES (src/directApi/engine) and its browser path, with or without a proxy. A new one is made by log new platform <name> (rule 83).
Source: docs/diagrams/platforms.mmd.
The browser layer
log-cli is browser-agnostic: each account's browser is a driver picked from Twenty, AdsPower first, and every driver opens through the same gates. Whether the gate needs a proxy is the platform's (PLATFORM_SITES). From BROWSER_DRIVERS (src/browser/driver) and commandBrowser (src/browser/run).
Source: docs/diagrams/browsers.mmd.
- The account's browser, through the driver:
facebook host,inspect,flow,<platform> warmup,facebook join,facebook read,facebook poll,facebook check,discord read,slack read. They run on whichever browser the account uses: AdsPower when it has a linked profile (socialAccounts.adsPowerProfileId), Camoufox otherwise, or whatsocialAccounts.browserDriversays. - Camoufox only, not on the driver yet:
facebook doctor,facebook connect,facebook profile,facebook scan,facebook status,facebook watch,facebook publish,facebook campaign,discord watch,slack watch,instagram watch. They launch Camoufox themselves (facebook-camofox-client's worker, or the publish, connect and profile sessions), so on an AdsPower account they don't open its AdsPower profile. Moving one onto the driver is moving it intoOPENER_ACTIONS(src/browser/run). - No browser:
launch,facebook listings,facebook listing,facebook accounts,facebook account,adsPower,localAccount,facebook lifecycle,facebook preflight,proxy,facebook conversations,facebook conversation,facebook messenger,errors,graph,facebook capture,runs,timing,inbox,media,browsers,job,mcp,login,logout,whoami,twitter session,twitter follows,twitter dms,twitter chat,twitter cookies,voice,search,kb,memelord,treg,balance,clone,transcribe,twitter followers,twitter notifications,twitter reply,twitter quote,twitter post,twitter like,twitter list,twitter repost,twitter follow,twitter dm,twitter react,twitter queue,new,reddit session,discord session,discord chats,discord send,discord delete,discord react,discord voice,linkedin session,linkedin cookies,slack session,slack workspaces,slack chats,slack send,slack cookies,slack react,slack voice,instagram session,instagram dms,instagram send,instagram react,instagram voice,twitter voice. - No browser path at all: X. Its accounts run on the direct API only (rule 80).
Each browser's capabilities, built or not: docs/browsers.md (generated, bun run check:browsers). AdsPower's setup: docs/adsPower.md.
Engines: the direct API first, the browser behind it
A command on the direct-API engine calls the platform's internal API with the account's saved session, paced by its own governor, and opens no browser. Each platform has its own vendored client: Facebook through fbk (vendor/fbk), X through twikit (vendor/twikit), Reddit through rdvwr (vendor/rdvwr), Discord through endcord (vendor/endcord), LinkedIn through openLinkedinApi (vendor/openLinkedinApi), Slack through gabrielSlack (vendor/gabrielSlack), Instagram through instagrapi (vendor/instagrapi). Every one reaches the network through one curl_cffi session (check:twikitTransport, check:rdvwrTransport). Which engine runs each command is code (DIRECT_ROUTES and commandEngine in src/directApi/engine), and this section, the Engine column and every MCP tool's description are generated from it (7 clients, 45 commands).
Facebook. Facebook's own page decides, not accountStatus: loggedIn reads; checkpoint stops and deactivates the account; loggedOut or accountWarning hands over to the browser path. Each account has its own governor (gaps of 4 s or more, 120 an hour, 500 a day, 40 writes a day, a 15-minute pause on a soft block, 6 hours on a checkpoint) and its result is written to socialAccounts.sessionState.
log facebook read: the direct-API engine first (fbk groups.feed_read, no browser) for read group without --live on a numeric group id, when the account has a saved c_user + xs session, a proxy in its home country and is inside its hours (--bypass skips the hours).
X. X's own answer decides: the session check's HTTP status first (401 or 403 is loggedOut, 200 or 404 loggedIn), then the call's own answer, read into one event (sent, dropped, throttled, dailyLimit, gone, automated, duplicate, sessionDead, restricted). Each account has its own governor under ~/.logCli/twitter/<account> (sends count as writes, 240 a day), refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log twitter session: the direct-API engine only (twikit, vendored in vendor/twikit, account.ping for the session check; no browser and no proxy: X accounts call X directly, rule 80).log twitter follows: the direct-API engine only (twikit, vendored in vendor/twikit, friendships.show for the follows read; no browser and no proxy: X accounts call X directly, rule 80).log twitter dms: the direct-API engine only (twikit, vendored in vendor/twikit, messages.inbox for the dms read (the inbox, or one conversation); no browser and no proxy: X accounts call X directly, rule 80).log twitter chat: the direct-API engine only (twikit, vendored in vendor/twikit, xchat.unlock_xchat for the chat pin (the PIN is checked against Juicebox before it is saved), chat watch (X pushes each message over wss://chat-ws.x.com, nothing polled); no browser and no proxy: X accounts call X directly, rule 80).log twitter cookies: the direct-API engine only (twikit, vendored in vendor/twikit, account.ping for the cookies save (the cookies are checked with X before they are saved); no browser and no proxy: X accounts call X directly, rule 80).log twitter followers: the direct-API engine only (twikit, vendored in vendor/twikit, friendships.followers for the followers read; no browser and no proxy: X accounts call X directly, rule 80).log twitter notifications: the direct-API engine only (twikit, vendored in vendor/twikit, notifications.get_notifications_page for the notifications read (new, or back through the history with --full); no browser and no proxy: X accounts call X directly, rule 80).log twitter reply: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.create_tweet for the reply; no browser and no proxy: X accounts call X directly, rule 80).log twitter quote: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.create_tweet for the quote; no browser and no proxy: X accounts call X directly, rule 80).log twitter post: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.create_tweet for the post (media uploaded first with upload_media); no browser and no proxy: X accounts call X directly, rule 80).log twitter like: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.favorite_tweet for the like; no browser and no proxy: X accounts call X directly, rule 80).log twitter list: the direct-API engine only (twikit, vendored in vendor/twikit, lists.get_lists, create_list, add_list_member for the list show, list create --live, list add --live; no browser and no proxy: X accounts call X directly, rule 80).log twitter repost: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.retweet for the repost; no browser and no proxy: X accounts call X directly, rule 80).log twitter follow: the direct-API engine only (twikit, vendored in vendor/twikit, users.follow_user for the follow; no browser and no proxy: X accounts call X directly, rule 80).log twitter dm: the direct-API engine only (twikit, vendored in vendor/twikit, messages.send_dm for the dm; no browser and no proxy: X accounts call X directly, rule 80).log twitter react: the direct-API engine only (twikit, vendored in vendor/twikit, messages.add_reaction_to_message for the react --live (--remove takes it off); no browser and no proxy: X accounts call X directly, rule 80).log twitter queue: the direct-API engine only (twikit, vendored in vendor/twikit, tweets.create_tweet for the queue run --live (each like and reply); no browser and no proxy: X accounts call X directly, rule 80).log twitter voice: the direct-API engine only (twikit, vendored in vendor/twikit, messages.SendMessageCreateMutation for the voice --live (a voice note in the voice cloned from matt, encrypted with chat-xdk and sent as an X Chat AUDIO attachment); no browser and no proxy: X accounts call X directly, rule 80).
Reddit. Reddit's own answer decides. Each account has its own governor under ~/.logCli/reddit/<account>, refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log reddit session: the direct-API engine only (rdvwr, vendored in vendor/rdvwr, account.ping for the session check; no browser).
Discord. Discord's own answer decides. Each account has its own governor under ~/.logCli/discord/<account>, refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log discord session: the direct-API engine only (endcord, vendored in vendor/endcord, account.ping for the session check; no browser).log discord chats: the direct-API engine only (endcord, vendored in vendor/endcord, messages.get_dms for the chats (the DMs and group DMs); no browser).log discord read: the direct-API engine only (endcord, vendored in vendor/endcord, messages.get_messages for the read (a chat or channel, paged with before); no browser).log discord watch: the direct-API engine only (endcord, vendored in vendor/endcord, gateway.Gateway for the watch (Discord pushes each message over wss://gateway.discord.gg, nothing polled); no browser).log discord send: the direct-API engine only (endcord, vendored in vendor/endcord, messages.send_message for the send --live; no browser).log discord delete: the direct-API engine only (endcord, vendored in vendor/endcord, messages.delete_message for the delete --live (only the account own messages, each read first); no browser).log discord react: the direct-API engine only (endcord, vendored in vendor/endcord, messages.send_reaction for the react --live (--remove takes it off); no browser).log discord voice: the direct-API engine only (endcord, vendored in vendor/endcord, messages.send_voice_message for the voice --live (a voice note in the voice cloned from matt, flags 8192); no browser).
LinkedIn. LinkedIn's own answer decides. Each account has its own governor under ~/.logCli/linkedin/<account>, refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log linkedin session: the direct-API engine only (openLinkedinApi, vendored in vendor/openLinkedinApi, account.ping for the session check; no browser).log linkedin cookies: the direct-API engine only (openLinkedinApi, vendored in vendor/openLinkedinApi, account.ping for the cookies save (the cookies are checked with LinkedIn before they are saved); no browser).
Slack. Slack's own answer decides. Each account has its own governor under ~/.logCli/slack/<account>, refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log slack session: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, account.exchange_cookie for the session check (and auth.test with --workspace); no browser).log slack workspaces: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, account.exchange_cookie for the workspaces (the ones the d cookie is signed in to); no browser).log slack chats: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, messages.conversations.list for the chats (the DMs and group DMs); no browser).log slack read: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, messages.conversations.history for the read (a chat, paged by cursor); no browser).log slack watch: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, websocket.connect for the watch (Slack pushes each event over wss-primary.slack.com, nothing polled); no browser).log slack send: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, messages.chat.postMessage for the send --live; no browser).log slack cookies: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, account.exchange_cookie for the cookies save (the d cookie is checked with Slack before it is saved); no browser).log slack react: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, messages.reactions.add for the react --live (--remove takes it off); no browser).log slack voice: the direct-API engine only (gabrielSlack, vendored in vendor/gabrielSlack, messages.files.completeUploadExternal for the voice --live (a voice note in the voice cloned from matt, uploaded then shared); no browser).
Instagram. Instagram's own answer decides. Each account has its own governor under ~/.logCli/instagram/<account>, refreshed cookies are merged back into its Twenty row, and the session state is written to socialAccounts.sessionState.
log instagram session: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, account.ping for the session check; no browser).log instagram dms: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, messages.direct_threads for the dms (the inbox, or one thread with --with); no browser).log instagram send: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, messages.direct_send for the send --live (text one message per line, and --photo through direct_send_photo); no browser).log instagram watch: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, websocket.realtime_connect for the watch (Instagram pushes each DM over its realtime MQTT, edge-mqtt.facebook.com, nothing polled); no browser).log instagram react: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, messages.direct_send_reaction for the react --live (--remove takes it off); no browser).log instagram voice: the direct-API engine only (instagrapi, vendored in vendor/instagrapi, messages.direct_send_voice for the voice --live (a voice note in the voice cloned from matt, its upload on curl_cffi); no browser).
How a call reaches log-cli
From a shell or over MCP, both run the same commands through runCli, write the same live log, and pass through the same hooks. commandEngine sends each command to the platform's direct API or to the account's browser.
Source: docs/diagrams/surface.mmd.
Every command
Every command also takes --proxyMs <ms> (this run's proxy speed limit, rule 33). --live acts on the platform and over MCP needs confirmLive: true; --bypass needs confirmBypass: true (rules 8, 10, 20).
| Command | What it does | Platform | Flags | Engine | Browser | MCP tool | Hooks: shell · MCP | Live log | Rules |
|---|---|---|---|---|---|---|---|---|---|
log launch | Start here: a guided menu to set log up (platforms, sign-ins, the account's browser and extensions), add an account, sign it in, check it and connect your agent (and Facebook Marketplace when Twenty is set up) | any (none) | --setup --json | none | none | terminal only: prompts in a terminal (Clack). Call the commands it wraps directly. | PreToolUse, PostToolUse · n/a | no | 30, 38, 39, 40, 41 |
log facebook listings | List the Twenty listings, by status, account or campaign | facebook first | --status <STATUS> --account <accountName> --campaign <campaignId> --json | none | none | listings | PreToolUse, PostToolUse · PostToolUse | no | 13, 30, 38, 39, 40, 41 |
log facebook listing | Show one Twenty listing, or create one for an account from a JSON file | facebook first | --json --account <accountName> --file <listing.json> --apply --live --bypass --now | none | none | listing, listingCreate, listingRemove, listingRemoveLive, listingSweep, listingLint | PreToolUse, PostToolUse · PostToolUse | no | 13, 71, 72, 30, 38, 39, 40, 41 |
log facebook accounts | List the FACEBOOK accounts in Twenty: status, home, proxy, and whether each may run | facebook first | --json | none | none | accounts | PreToolUse, PostToolUse · PostToolUse | no | 13, 30, 38, 39, 40, 41 |
log facebook account | Show one account, or set its status (banned/dead/restricted replace its campaign listings) | facebook first | --json --from <folder> --reason <text> --detail <text> | none | none | accountShow, accountResave, accountResetOpens, accountStatus | PreToolUse, PostToolUse · PostToolUse | no | 13, 37, 78, 30, 38, 39, 40, 41 |
log adsPower | AdsPower: Local API status, its profiles, and one profile per Twenty account (sync) | any (none) | --json --apply --account <accountName> --homes <US,IE> | none | none | adsPower | PreToolUse, PostToolUse · PostToolUse | no | 42, 44, 78, 84, 30, 38, 39, 40, 41 |
log localAccount | Accounts kept in a local file instead of Twenty: add one, with the account's browser profile, pull every Twenty account in, or list them | any (none) | `--platform <facebook | discord | slack> --account --adsPower --proxy scheme://user:pass@host:port --home --city --json --refresh` | none | |||
log facebook lifecycle | Each account's place in its lifecycle (rule 35): status, state, what onboarding has left; states prints the machine | facebook first | --account <name> --json --write | none | none | lifecycle | PreToolUse, PostToolUse · PostToolUse | no | 35, 30, 38, 39, 40, 41 |
log facebook preflight | Before any browser opens, check each account will get through: home timezone, hours, proxy speed and country, the account's browser and its kernel, launches and host opens left, no session left open, its picture on disk | facebook first | --account <accountName> --homes <US,CA> --noProxy --apply --json | none | none | preflight | PreToolUse, PostToolUse · PostToolUse | no | 12, 18, 24, 33, 35, 37, 79, 30, 38, 39, 40, 41 |
log facebook doctor | Check python, facebook-camofox-client, Camoufox, Twenty, and which accounts may run | facebook first | --json | browser | Camoufox only | doctor | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 12, 30, 38, 39, 40, 41 |
log facebook connect | Sign a Facebook account in through its proxy (the connect machine), save its cookies to Twenty | facebook first | --account <accountName> --noPrompt --wait <seconds> --bypass --now | browser | Camoufox only | connect | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 12, 18, 21, 23, 24, 73, 30, 38, 39, 40, 41 |
log facebook profile | Set an account's profile picture and cover photo from Twenty (dry run unless --live, rule 28) | facebook first | --account <accountName> --live --bypass --json | browser | Camoufox only | profile, profileLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 28, 73, 30, 38, 39, 40, 41 |
log facebook host | Run an account's lifecycle in ONE browser kept open for its day, resuming where it left (rule 35); send it events; read its progress | facebook first | --account <accountName> --live --likes --bypass --now --gapSeconds <n> --watchMinutes <n> | browser | the account's | hostStart, hostStartLive, hostSend, hostStatus | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 18, 35, 73, 79, 30, 38, 39, 40, 41 |
log inspect | Drive the account's browser, gated and headed, a step at a time: each step's snapshot, screenshot, HAR and rea, recorded as it happens (rule 29) | any (none) | --account <accountName> --goal <text> --url <https://www.facebook.com/... or the platform's own page> --bypass --now --noIdleMouse --detach --all --live --timeout <s> --json --set <text> --step <step> --point <text> --explain <text> --evidence <text> --fixes <n> --fix <text> | browser | the account's | inspectStart, inspectDo, inspectDoLive, inspectRead, inspectMap, inspectGoal, inspectFinding, inspectSessions | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 12, 17, 22, 24, 26, 29, 30, 35, 48, 54, 55, 68, 72, 73, 74, 75, 82, 84, 30, 38, 39, 40, 41 |
log flow | Recorded inspect runs as flows: docs, an XState machine per flow, run on any account (rule 31) | any (none) | --name <name> --summary <text> --rename --account <accountName> --set <param> --live --bypass --now --json | browser | the account's | flow, flowLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 29, 31, 68, 72, 73, 79, 30, 38, 39, 40, 41 |
log <platform> warmup | The warm-up, by platform and activity: log facebook warmup reel|feed|stages (the account's browser primed with the activity, each step checked, the outline written; rules 49, 60) | <platform> first | --account <a> --live --likes --bypass --now --json --newPersona --seed <n> --reels --watch --skip --comments --profile --persona --trafficMb --posts --only --gapSeconds <n> --lines <n> --follow --seconds <n> | browser | the account's | warmup, warmupLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 18, 20, 25, 26, 29, 32, 35, 36, 49, 50, 51, 54, 55, 60, 64, 73, 30, 38, 39, 40, 41 |
log facebook join | Join a Facebook group as one account, in its open browser: read the gate, --live presses Join; every step patches agencyGroups.joinSubmission (rule 45) | facebook first | --account <accountName> --group <group url, id or name> --live --json --show --answer --set <n> --agree --fill --submit --bypass | browser | the account's | join, joinLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 10, 18, 20, 25, 26, 29, 35, 45, 64, 73, 30, 38, 39, 40, 41 |
log facebook read | Read a Facebook group's new posts and their comments, or its newest members, as one account, by hand: posts on the direct-API engine first, else in its open browser; only the group's cursor is saved to Twenty (#27, rule 65), the members to a file | facebook first | --account <accountName> --group <group url or id> --scrolls <n> --commentPosts <n> --live --bypass --json --show --worker --count <n> --secondsPerScroll <s> --maxScrolls <n> --owners | direct API first | the account's | readGroup, readGroupLive, readMembers | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 13, 18, 20, 22, 25, 26, 29, 35, 45, 54, 63, 64, 65, 73, 77, 30, 38, 39, 40, 41 |
log facebook poll | Poll a Facebook group from one account, in its open browser, for a time or once: each poll's posts as one JSON line, OpenMagpie's records; --request answers OpenMagpie's facebook_group worker (#27, rule 63) | facebook first | --bypass --request <request json> | browser | the account's | poll | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 13, 18, 22, 26, 35, 45, 63, 64, 65, 73, 30, 38, 39, 40, 41 |
log facebook check | Check one account in its open browser: may it still sell, each listing's state on Selling (into Twenty), and its seller inbox's chats | facebook first | --account <accountName> --bypass --json | browser | the account's | check | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 13, 18, 22, 29, 35, 69, 73, 30, 38, 39, 40, 41 |
log facebook scan | Read Marketplace > Selling live for one account and update its listings in Twenty | facebook first | --account <accountName> --json --bypass | browser | Camoufox only | scan | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 12, 18, 22, 73, 30, 38, 39, 40, 41 |
log facebook status | Open one Marketplace item page live and classify its status | facebook first | --account <accountName> --json --bypass | browser | Camoufox only | status | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 12, 18, 22, 73, 30, 38, 39, 40, 41 |
log facebook watch | Run the listing monitor (XState) for one account and print every step | facebook first | --account <accountName> --checks <n> --interval <minutes> --json --bypass | browser | Camoufox only | watch | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 6, 18, 73, 30, 38, 39, 40, 41 |
log proxy | Check an account's proxy, give it one from a provider (2Captcha, Byteful) in its home city, or see providers' traffic | any (none) | --account <accountName> --json --missing --vendor --city <city> --region <state> --anyCity --minutes <1-120> --socks5 | none | none | proxy | PreToolUse, PostToolUse · PostToolUse | no | 12, 33, 41, 43, 51, 52, 30, 38, 39, 40, 41 |
log facebook publish | Post one Twenty listing (dry run unless --live), printing every machine step | facebook first | --listing <agencyListingId> --live --json --formUrl <url> --bypass --now | browser | Camoufox only | publish, publishLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 8, 10, 12, 18, 22, 24, 68, 72, 73, 30, 38, 39, 40, 41 |
log facebook campaign | Put one ad on N accounts in one place, replace lost accounts, publish the campaign | facebook first | --campaign <agencyCampaignId> --from <agencyListingId> --accounts <n> --live --bypass --json | browser | Camoufox only | campaign, campaignLive | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 13, 18, 71, 72, 30, 38, 39, 40, 41 |
log facebook conversations | List buyers' Marketplace conversations in Twenty, newest first, by account or listing | facebook first | --account <name> --listing <agencyListingId> --json | none | none | conversations | PreToolUse, PostToolUse · PostToolUse | no | 34, 30, 38, 39, 40, 41 |
log facebook conversation | One conversation: show its messages, mark read, set its lead status, link its listing, or store a scanned thread | facebook first | --json --listing <agencyListingId> --account <name> --file <threads.json> | none | none | conversationShow, conversationRead, conversationLead, conversationLink, conversationRecord | PreToolUse, PostToolUse · PostToolUse | no | 34, 30, 38, 39, 40, 41 |
log facebook messenger | Messenger's PIN for the account's encrypted chats: asked in a Clack prompt, kept in Twenty, never printed | facebook first | --account <accountName> --show | none | none | terminal only: asks matt for the Messenger PIN in a Clack prompt (masked). He runs log facebook messenger pin --account <a> in a terminal. | PreToolUse, PostToolUse · n/a | no | 8, 21, 29, 54, 63, 67, 30, 38, 39, 40, 41 |
log errors | List every error code: meaning, retryable, where each machine ends up | any (none) | `--layer <account | listing | photo | form | captcha | runtime | twenty |
log graph | Print a machine's states and transitions as a Mermaid diagram | any (none) | none | none | none | graph | PreToolUse, PostToolUse · PostToolUse | no | 6, 25, 30, 38, 39, 40, 41 |
log facebook capture | Read a run's raw HAR (any browser driver) and its scripts with offline rea; --docs builds every platform's client docs, rea reading each request's code. No browser. | facebook first | --har <path> --out <dir> --noRea --json --settle <runCaptureDir> --docs | none | none | capture | PreToolUse, PostToolUse · PostToolUse | yes: watch it | 19, 22, 26, 27, 46, 48, 85, 30, 38, 39, 40, 41 |
log runs | The recorded runs on this machine, and pruning the ones rea settled and the request docs hold, keeping each one's findings and domains | any (none) | --json --before <YYYY-MM-DD> --apply | none | none | runsList, runsExchanges, runsPrune, runsSpace | PreToolUse, PostToolUse · PostToolUse | no | 19, 22, 25, 27, 66, 73, 30, 38, 39, 40, 41 |
log timing | Where the time goes in every call: p50, p95 and p99 per tool and per stage, from the timings every call stores in log.db, or one call as a waterfall | any (none) | --tool <name> --hours <n> --stages <n> --json --call <callId> | none | none | timing | PreToolUse, PostToolUse · PostToolUse | no | 26, 73, 165, 30, 38, 39, 40, 41 |
log inbox | Claim a chat before acting on it, release it, list the live claims, and read the log of every action taken in a chat | any (none) | `--platform <twitter | discord | slack | instagram> --account --chat <conversationId | handle> --event --json --by --limit ` | none | none |
log media | Describe every image, meme and video with one local vision model into the .db, so replies read what was sent | any (none) | --json --wait <seconds> | none | none | mediaDescriber, mediaDescribe, mediaStatus | PreToolUse, PostToolUse · PostToolUse | no | 26, 41, 126, 131, 137, 138, 139, 30, 38, 39, 40, 41 |
log browsers | Where every open browser is: its page, the known page state it sits on, the call that resolves it, and what only matt can clear (rules 54-55) | any (none) | --json | none | none | browsers | PreToolUse, PostToolUse · PostToolUse | no | 29, 35, 54, 55, 30, 38, 39, 40, 41 |
log job | Follow or stop a long MCP run: each runs as a job when it outlasts its call's wait | any (none) | --afterLine <n> --waitMs <ms> --json | none | none | jobStatus, jobCancel, jobList | PreToolUse, PostToolUse · PostToolUse | no | 10, 26, 35, 30, 38, 39, 40, 41 |
log mcp | This server's MCP sign-ins over OAuth (through Twenty, the owner only): list the clients, revoke them | any (none) | --json | none | none | terminal only: manages who may sign in to this server's MCP (OAuth through Twenty). matt runs log mcp clients or mcp revoke <clientId|all> in a terminal on the server. | PreToolUse, PostToolUse · n/a | no | 21, 47, 167, 30, 38, 39, 40, 41 |
log login | Only for a team on Twenty: sign in to its Twenty through its sign-in app (OAuth). Local accounts need no sign-in, and without Twenty set up it says so | any (none) | --webUrl <url> --apiUrl <url> --token <accessToken> --json | none | none | terminal only: opens a browser for Twenty OAuth and waits for it. Run log login in a terminal. | PreToolUse, PostToolUse · n/a | no | 30, 38, 39, 40, 41 |
log logout | Forget the stored Twenty session | any (none) | --apiUrl <url> --json | none | none | logout | PreToolUse, PostToolUse · PostToolUse | no | 30, 38, 39, 40, 41 |
log whoami | Show who is signed in, refreshing the session if it expired | any (none) | --apiUrl <url> --json | none | none | whoami | PreToolUse, PostToolUse · PostToolUse | no | 30, 38, 39, 40, 41 |
log twitter session | Check that X still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty | twitter first | --account <accountName> --json | direct API only | none | twitterSessionCheck, twitterSessionWebClient | PreToolUse, PostToolUse · PostToolUse | no | 13, 21, 26, 63, 64, 73, 80, 81, 104, 105, 113, 150, 30, 38, 39, 40, 41 |
log twitter follows | Read whether handles follow an X account and it follows them, on the direct-API engine, sending nothing | twitter first | --account <accountName> --handles <handle,handle…> --json | direct API only | none | twitterFollows | PreToolUse, PostToolUse · PostToolUse | no | 13, 53, 63, 64, 73, 80, 81, 104, 105, 30, 38, 39, 40, 41 |
log twitter dms | Read an X account's direct messages and who is waiting on a reply, on the direct-API engine, sending nothing | twitter first | --account <accountName> --limit <n> --waiting --with <handle> --conversation <id> --full --force --json | direct API only | none | twitterDms | PreToolUse, PostToolUse · PostToolUse | no | 13, 63, 64, 73, 80, 81, 88, 94, 100, 104, 105, 108, 109, 110, 113, 126, 30, 38, 39, 40, 41 |
log twitter chat | X Chat's PIN for the account's encrypted DMs: asked in a Clack prompt, kept in Twenty, never printed | twitter first | --account <accountName> --show --seconds <n> --inbox --json | direct API only | none | terminal only: asks matt for the X Chat PIN in a Clack prompt (masked). He runs log twitter chat pin --account <a> in a terminal. | PreToolUse, PostToolUse · n/a | no | 13, 21, 26, 63, 64, 73, 80, 81, 88, 94, 104, 105, 131, 132, 30, 38, 39, 40, 41 |
log twitter cookies | Add or refresh an X account in Twenty: its session cookies from a masked prompt, checked against X, and its copy style | twitter first | --account <handle> --style <copyStyle> --name <label> | direct API only | none | terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal. | PreToolUse, PostToolUse · n/a | no | 13, 21, 63, 64, 73, 80, 81, 104, 105, 30, 38, 39, 40, 41 |
log voice | Gate a draft against matt's own writing guide with SubQ before anything is sent as him, sending nothing | any (none) | --to <handle> --text --context --with <person or chat> --spoken --json | none | none | voice, voiceAiIndex, voiceIndex | PreToolUse, PostToolUse · PostToolUse | no | 30, 39, 41, 63, 64, 98, 112, 127, 149, 30, 38, 39, 40, 41 |
log search | Hybrid search across every conversation (BM25 keyword + vector meaning, fused with RRF), and building its indexes | any (none) | --limit <n> --platform <p> --account <a> --with <handle> --after <YYYY-MM-DD> --json --saved --rebuild --rounds <n> | none | none | search, searchIndex, searchBench | PreToolUse, PostToolUse · PostToolUse | no | 13, 26, 63, 64, 88, 89, 105, 30, 38, 39, 40, 41 |
log kb | The knowledge base: search it, sync every source into Twenty and Redis, remember a note, show an article | any (none) | --kind <kind> --limit <n> --json --noAccounts --title <t> --tags <a,b> | none | none | kbSearch, kbSync, kbRemember, kbShow | PreToolUse, PostToolUse · PostToolUse | no | 13, 26, 30, 93, 30, 38, 39, 40, 41 |
log memelord | The Memelord extension: search memes for a moment, read one template, see what is trending, list the image tools (free tools only) | any (none) | --type --method --limit <n> --json --media --category --build --from <posts.json> --noTaste | none | none | memelordSearch, memelordTemplate, memelordTrending, memelordTools, memelordTaste | PreToolUse, PostToolUse · PostToolUse | no | 41, 94, 119, 120, 30, 38, 39, 40, 41 |
log treg | The treg extension: find an endpoint by the job, read it, and call it with the key injected server-side (free ones run, priced ones only when matt asked) | any (none) | --limit <n> --json --params --path <path> `--method <GET | POST> --data --await --approvedBy --live` | none | none | tregSearch, tregGet, tregCall, tregCallLive | PreToolUse, PostToolUse · PostToolUse | no |
log balance | Every paid provider log calls, with what is left and which are out of credit | any (none) | --provider <name> --json | none | none | balance | PreToolUse, PostToolUse · PostToolUse | no | 41, 73, 94, 164, 30, 38, 39, 40, 41 |
log clone | matt's voice, cloned once per provider (Inworld or Fish Audio) from his own voice notes, and words said in it to a local file | any (none) | --provider --from <file> --apply --replace --json --text --model <model> --format | none | none | cloneVoice, cloneSpeak | PreToolUse, PostToolUse · PostToolUse | no | 39, 41, 94, 106, 119, 142, 143, 145, 146, 147, 30, 38, 39, 40, 41 |
log transcribe | Transcribe every voice note in the saved X and Discord conversations with AssemblyAI, written into the conversation store for search, voice and SubQ | any (none) | --platform --account <a> --list --noIndex --json | none | none | transcribe, transcribeFile | PreToolUse, PostToolUse · PostToolUse | no | 26, 41, 88, 89, 94, 105, 106, 126, 30, 38, 39, 40, 41 |
log twitter followers | Read an X account's own followers on the direct-API engine, sending nothing | twitter first | --account <accountName> --limit <n> --json | direct API only | none | twitterFollowers | PreToolUse, PostToolUse · PostToolUse | no | 13, 63, 64, 73, 80, 81, 104, 105, 30, 38, 39, 40, 41 |
log twitter notifications | Read who liked, reposted, replied to, quoted or followed an X account, new and historical, kept locally, sending nothing | twitter first | --account <accountName> --type --since <date> `--post <id | url> --full --pages --stored --limit --json` | direct API only | none | twitterNotifications | PreToolUse, PostToolUse · PostToolUse | no |
log twitter reply | Reply to one post on X with approved copy, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --tweet <postUrl> --text --media <file> --approvedBy <who> --skipStyleCheck --force --live --json | direct API only | none | twitterReply, twitterReplyLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 53, 63, 64, 73, 80, 81, 86, 94, 104, 105, 121, 30, 38, 39, 40, 41 |
log twitter quote | Quote one post on X with approved copy, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --tweet <postUrl> --text --approvedBy <who> --skipStyleCheck --force --live --json | direct API only | none | twitterQuote, twitterQuoteLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 53, 63, 64, 73, 80, 81, 86, 94, 104, 105, 30, 38, 39, 40, 41 |
log twitter post | Make one original post on X with approved copy and optional media (a video, a gif or up to 4 images), on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --text --media <file> --approvedBy <who> --skipStyleCheck --force --live --json | direct API only | none | twitterPost, twitterPostLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 63, 64, 73, 80, 81, 86, 94, 104, 105, 30, 38, 39, 40, 41 |
log twitter like | Like one post on X, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --tweet <postUrl> --approvedBy <who> --force --live --json | direct API only | none | twitterLike, twitterLikeLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 53, 63, 64, 73, 80, 81, 86, 94, 104, 105, 30, 38, 39, 40, 41 |
log twitter list | The X account's own lists: show them, make one, put someone on one; changes are dry runs unless --live | twitter first | --account <accountName> --json --name <name> --description <text> --private --approvedBy <who> --live `--list <name | id> --handle --userId ` | direct API only | none | twitterListShow, twitterListCreate, twitterListCreateLive, twitterListAdd, twitterListAddLive | PreToolUse, PostToolUse · PostToolUse | no |
log twitter repost | Repost one post on X, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --tweet <postUrl> --approvedBy <who> --force --live --json | direct API only | none | twitterRepost, twitterRepostLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 53, 63, 64, 73, 80, 81, 86, 94, 104, 105, 30, 38, 39, 40, 41 |
log twitter follow | Follow one person on X, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --userId <id> --approvedBy <who> --force --live --json | direct API only | none | twitterFollow, twitterFollowLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 53, 63, 64, 73, 80, 81, 86, 94, 104, 105, 30, 38, 39, 40, 41 |
log twitter dm | Send one approved direct message on X, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --userId <id> --text `--media <file | url> --approvedBy --skipStyleCheck --asOne --overGate --force --live --json` | direct API only | none | twitterDm, twitterDmLive | PreToolUse, PostToolUse · PostToolUse | no |
log twitter react | React to a X message with an emoji, on the direct-API engine; a dry run unless --live | twitter first | --account <accountName> --chat <chat> --message <id> --emoji <emoji> --remove --approvedBy <who> --live --json | direct API only | none | twitterReact, twitterReactLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 63, 64, 73, 80, 81, 94, 104, 105, 133, 140, 30, 38, 39, 40, 41 |
log twitter queue | Build, approve, plan and run a queue of approved X replies, quotes or dms at random moments, on the direct-API engine | twitter first | --targets <targets.json> --variations <a.json,b.json> --out <queue.json> --account <accountName> --action --seed <n> --json --skipXChatCheck --by <who> `--window <5h | 90m> --limit --start --minGap --like --likeChance <0-1> --alertChannel --alertReplyTo --alertMention <hex,hex> --force --keepOpen --live --replies --n --text` | direct API only | none | twitterQueueBuild, twitterQueueShow, twitterQueueCheck, twitterQueueStatus, twitterQueueApprove, twitterQueuePlan, twitterQueueRun, twitterQueueRunLive, twitterQueueAdd, twitterQueueNudge, twitterQueueEdit | PreToolUse, PostToolUse · PostToolUse | yes: watch it |
log new | Make a new platform (session, command, MCP tool, engine, docs), wired into the repo through its syntax tree | any (none) | --label <shown name> --twentyPlatform <ENUM> --library <vendor/folder> --sessionCookies <a,b> --cookieDomain <.site.com> --hosts <a.com,b.com> --home <https://…> --noProxy --noBrowser --apply --json | none | none | newPlatform | PreToolUse, PostToolUse · PostToolUse | no | 1, 2, 63, 64, 83, 30, 38, 39, 40, 41 |
log reddit session | Check that Reddit still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty | reddit first | --account <accountName> --json | direct API only | none | redditSessionCheck | PreToolUse, PostToolUse · PostToolUse | no | 13, 63, 64, 73, 83, 30, 38, 39, 40, 41 |
log discord session | Check that Discord still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty | discord first | --account <accountName> --json | direct API only | none | discordSessionCheck | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 94, 30, 38, 39, 40, 41 |
log discord chats | List the account's DMs and group DMs with their ids, on the direct-API engine, sending nothing | discord first | --account <accountName> --json | direct API only | none | discordChats | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 94, 30, 38, 39, 40, 41 |
log discord read | Read a Discord chat or channel's latest messages, on the direct-API engine, sending nothing | discord first | --account <accountName> `--channel <channelId | chat name> --limit --after --json` | direct API only | the account's | discordRead | PreToolUse, PostToolUse · PreToolUse, PostToolUse | yes: watch it |
log discord watch | Watch the account's Discord messages as Discord pushes them over its gateway, nothing polled, sending nothing | discord first | --account <accountName> `--channel <channelId | chat name> --seconds --inbox --json` | direct API only | Camoufox only | discordWatch | PreToolUse, PostToolUse · PreToolUse, PostToolUse | yes: watch it |
log discord send | Send one message to a Discord DM, group or channel, on the direct-API engine; a dry run unless --live | discord first | --account <accountName> `--channel <channelId | chat name> --text --asOne --overGate --approvedBy --live --json --brief --file` | direct API only | none | discordSend, discordSendLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no |
log discord delete | Delete the account's own Discord messages by id, on the direct-API engine; a dry run unless --live | discord first | --account <accountName> `--channel <channelId | chat name> --message --approvedBy --live --json` | direct API only | none | discordDelete, discordDeleteLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no |
log discord react | React to a Discord message with an emoji, on the direct-API engine; a dry run unless --live | discord first | --account <accountName> --channel <channel> --message <id> --emoji <emoji> --remove --approvedBy <who> --live --json | direct API only | none | discordReact, discordReactLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 122, 133, 140, 30, 38, 39, 40, 41 |
log discord voice | Send a voice note in matt's cloned voice as Discord's own voice note, from text; a dry run unless --live | discord first | --account <accountName> --channel <channel> --text --provider --mattsWords --translationOf --language <code> --approvedBy <who> --live --json | direct API only | none | discordVoice, discordVoiceLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 122, 133, 135, 137, 142, 143, 144, 145, 146, 147, 148, 149, 30, 38, 39, 40, 41 |
log linkedin session | Check that LinkedIn still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty | linkedin first | --account <accountName> --json | direct API only | none | linkedinSessionCheck | PreToolUse, PostToolUse · PostToolUse | no | 13, 63, 64, 73, 83, 102, 30, 38, 39, 40, 41 |
log linkedin cookies | Add or refresh a LinkedIn account in Twenty: its session cookies from a masked prompt, checked against LinkedIn | linkedin first | --account <publicId> --name <label> | direct API only | none | terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal. | PreToolUse, PostToolUse · n/a | no | 13, 21, 63, 64, 73, 83, 102, 30, 38, 39, 40, 41 |
log slack session | Check that Slack still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to its row | slack first | --account <accountName> --workspace <domain> --json | direct API only | none | slackSessionCheck | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 125, 30, 38, 39, 40, 41 |
log slack workspaces | List the workspaces a Slack account is signed in to, on the direct-API engine, sending nothing | slack first | --account <accountName> --json --workspace | direct API only | none | slackWorkspaces | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 125, 30, 38, 39, 40, 41 |
log slack chats | List a Slack account's DMs and group DMs in a workspace with their ids, on the direct-API engine, sending nothing | slack first | --account <accountName> --workspace <domain> --limit <n> --json | direct API only | none | slackChats | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 94, 125, 30, 38, 39, 40, 41 |
log slack read | Read a Slack chat's latest messages, on the direct-API engine, sending nothing | slack first | --account <accountName> `--chat <chatId | who it is with> --workspace --limit --json` | direct API only | the account's | slackRead | PreToolUse, PostToolUse · PreToolUse, PostToolUse | yes: watch it |
log slack watch | Watch a Slack workspace's events as its websocket pushes them, nothing polled, sending nothing | slack first | --account <accountName> --workspace <domain> `--chat <chatId | who it is with> --seconds --inbox --json` | direct API only | Camoufox only | slackWatch | PreToolUse, PostToolUse · PreToolUse, PostToolUse | yes: watch it |
log slack send | Send a message to a Slack DM, group or channel, on the direct-API engine; a dry run unless --live | slack first | --account <accountName> `--chat <chatId | who it is with> --text --workspace --asOne --approvedBy --live --json` | direct API only | none | slackSend, slackSendLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no |
log slack cookies | Add or refresh a Slack account locally: its d cookie from a masked prompt, checked against Slack | slack first | --account <accountName> --name <label> --workspace <domain> | direct API only | none | terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal. | PreToolUse, PostToolUse · n/a | no | 13, 21, 63, 64, 73, 83, 97, 125, 30, 38, 39, 40, 41 |
log slack react | React to a Slack message with an emoji, on the direct-API engine; a dry run unless --live | slack first | --account <accountName> --chat <chat> --message <id> --emoji <emoji> --workspace <workspace> --remove --approvedBy <who> --live --json | direct API only | none | slackReact, slackReactLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 125, 133, 140, 30, 38, 39, 40, 41 |
log slack voice | Send a voice note in matt's cloned voice as Slack's own voice note, from text; a dry run unless --live | slack first | --account <accountName> --chat <chat> --text --workspace <workspace> --provider --mattsWords --translationOf --language <code> --approvedBy <who> --live --json | direct API only | none | slackVoice, slackVoiceLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 125, 133, 135, 137, 142, 143, 144, 145, 146, 147, 148, 149, 30, 38, 39, 40, 41 |
log instagram session | Check that Instagram still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty | instagram first | --account <accountName> --json | direct API only | none | instagramSessionCheck | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 13, 63, 64, 73, 83, 129, 30, 38, 39, 40, 41 |
log instagram dms | Read an Instagram account's DMs: its threads, or one thread's messages with --with, on the direct-API engine, sending nothing | instagram first | --account <accountName> `--with <handle | thread name | thread id> --limit --json` | direct API only | none | instagramDms | PreToolUse, PostToolUse · PreToolUse, PostToolUse |
log instagram send | Send text and a photo to an Instagram DM, on the direct-API engine; a dry run unless --live | instagram first | --account <accountName> `--with <handle | thread name | thread id> --text --photo --asOne --approvedBy --live --json` | direct API only | none | instagramSend, instagramSendLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse |
log instagram watch | Watch an Instagram account's DMs as Instagram's realtime connection pushes them, nothing polled, sending nothing | instagram first | --account <accountName> --seconds <s> --inbox --json | direct API only | Camoufox only | instagramWatch | PreToolUse, PostToolUse · PreToolUse, PostToolUse | yes: watch it | 13, 26, 63, 64, 73, 83, 94, 123, 129, 131, 132, 30, 38, 39, 40, 41 |
log instagram react | React to a Instagram message with an emoji, on the direct-API engine; a dry run unless --live | instagram first | --account <accountName> --thread <thread> --message <id> --emoji <emoji> --remove --approvedBy <who> --live --json | direct API only | none | instagramReact, instagramReactLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 128, 129, 133, 140, 30, 38, 39, 40, 41 |
log instagram voice | Send a voice note in matt's cloned voice as Instagram's own voice note, from text; a dry run unless --live | instagram first | --account <accountName> --with <with> --text --provider --mattsWords --translationOf --language <code> --approvedBy <who> --live --json | direct API only | none | instagramVoice, instagramVoiceLive | PreToolUse, PostToolUse · PreToolUse, PostToolUse | no | 8, 13, 63, 64, 73, 83, 94, 128, 129, 133, 135, 137, 142, 143, 144, 145, 146, 147, 148, 149, 30, 38, 39, 40, 41 |
log twitter voice | Send a voice note in matt's cloned voice as Twitter's own voice note, from text; a dry run unless --live | twitter first | --account <accountName> --with <with> --text --provider --mattsWords --translationOf --language <code> --approvedBy <who> --live --json | direct API only | none | twitterVoice, twitterVoiceLive | PreToolUse, PostToolUse · PostToolUse | no | 8, 13, 63, 64, 73, 80, 81, 88, 94, 104, 105, 133, 135, 137, 142, 143, 144, 145, 146, 147, 148, 149, 151, 30, 38, 39, 40, 41 |
Saved flows: MCP tools of their own (rule 31)
| MCP tool | Runs | What it does | Params | Steps |
|---|---|---|---|---|
flowDeletePost, flowDeletePostLive | log flow run deletePost | Delete the newest post on the account's own profile: its Actions menu, Move to bin, Move (useCometTrashPostMutation; Facebook empties the bin after 30 days). Writes on Facebook (--live). | none | gotoprofilePhp → clickActionsForThisPost → clickMoveToBinItems → clickMove |
flowFacebookLanguageEnglish, flowFacebookLanguageEnglishLive | log flow run facebookLanguageEnglish | Switch the account's Facebook language to English (US): Settings, Language and region, Account language, English (US), OK; decided by useCometUSFScrollablePickerMutation answering en_US. Recorded on Scranton 2026-10-25 from Arabic. | none | gotosettings → click → clickEnglishUs → click |
flowGroupHeaderMenus, flowGroupHeaderMenusLive | log flow run groupHeaderMenus | Open and close the group header's search, More menu, Joined menu, Invite dialog and Share sheet; nothing inside them is picked | groupId | gotogroups → clickSearchWithinThisGroup → pressEscape → clickMore → pressEscape → clickJoined → pressEscape → clickInvite → pressEscape → clickShareGroup → pressEscape |
flowGroupMembers, flowGroupMembersLive | log flow run groupMembers | Open a group's People tab and scroll it read-only so Facebook loads the member list (bio line, page category) for extraction | none | gotogroupsJunkremovalwinners → gotogroupsJunkremovalwinnersMembers → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll → scroll |
flowGroupPostComments, flowGroupPostCommentsLive | log flow run groupPostComments | On a group's Open questions tab, open a post's comments, switch the sort to All comments and scroll them; read-only | groupId | gotogroupsQuestions → clickLeaveAComment → clickMostRelevant → clickAllCommentsShowAll → scroll |
flowGroupPostDialog, flowGroupPostDialogLive | log flow run groupPostDialog | On a group's Discussion feed, open a post in its dialog with Leave a comment (the comment box takes focus, nothing is typed) and close it | groupId | gotogroups → clickLeaveAComment → clickClose |
flowGroupTabs, flowGroupTabsLive | log flow run groupTabs | Open each tab of a group the account is in (Featured, Open questions, People, Events, Media, Files, About) and come back to Discussion; read-only | groupId | gotogroups → clickFeatured → clickOpenQuestions → clickPeople → clickEvents → clickMedia → clickFiles → clickAbout → clickDiscussion |
flowMarketplaceListingDelete, flowMarketplaceListingDeleteLive | log flow run marketplaceListingDelete | Delete one Marketplace listing from the Selling page: its More options menu, Delete listing, Delete, then "No, haven't sold" on Facebook's survey. Recorded on Bethlehem, 2026-10-23 (listing 1425935622848350). | listingTitle, listingId | gotomarketplaceYouSelling → clickMoreOptions → clickDeleteListing → clickDelete → clickNoHavenTSold → clickNext |
flowMarketplaceListingPublish, flowMarketplaceListingPublishLive | log flow run marketplaceListingPublish | Publish one Marketplace listing in the account's open AdsPower browser: set the Marketplace location (City, State written out), open Create, two photos, title, price, category Miscellaneous, condition New, description, Next, Publish, close the Boost offer. Recorded on Bethlehem 2026-10-22 (listing 1425935622848350, held for review). | location, photo1, photo2, title, price, description | gotomarketplace → setLocation → gotomarketplaceCreateItem → uploadAddPhotosOrDrag → uploadAddPhoto → fillTitle → fillPrice → clickCategory → clickMiscellaneous → clickCondition → clickNew → fillDescription → clickNext → clickPublish → clickClose |
flowMessengerPinCreate, flowMessengerPinCreateLive | log flow run messengerPinCreate | Messenger's Create a PIN: open /messages/, press Create PIN, type the account's PIN from Twenty, then again to confirm (rule 67). Recorded on Bethlehem 2026-10-22. | none | gotomessages → clickCreatePin → pin → pin |
flowPostComment, flowPostCommentLive | log flow run postComment | Comment on the newest post on the account's own profile: type in its Write a comment box, Post comment (useCometUFICreateCommentMutation). Writes on Facebook (--live). | comment | gotoprofilePhp → fillWriteAComment → clickPostComment |
flowProfilePhotoPost, flowProfilePhotoPostLive | log flow run profilePhotoPost | Post a photo on the account's own profile: Photo/video hands the file to the chooser, the upload finishes, Post, wait for ComposerStoryCreateMutation. Writes on Facebook (--live). | photo | gotoprofilePhp → uploadPhotoVideo → clickPost |
flowProfilePost, flowProfilePostLive | log flow run profilePost | Post text on the account's own profile: open the composer, type the text, Post, wait for ComposerStoryCreateMutation. Writes on Facebook (--live). | text | gotoprofilePhp → clickWhatSOnYour → fillWhatSOnYour → clickPost |
flowProfileSetup, flowProfileSetupLive | log flow run profileSetup | Give an account its profile picture, cover photo and name, as recorded on Lancaster 2026-10-19 (rule 28): profile picture menu, Choose profile picture, Upload Photo, Save; Add Cover Photo, Upload photo, Save changes; Accounts Center name, Review change, Done. | profilePhoto, coverPhoto, firstName, lastName | gotome → clickProfilePictureActions → clickChooseProfilePicture → uploadUploadPhoto → clickSave → clickAddCoverPhoto → uploadUploadPhoto → clickSaveChanges → gotoprofilesName → fillFirstName → fillSurname → clickReviewChange → clickDone |
flowWarmupCreateAccessCheck, flowWarmupCreateAccessCheckLive | log flow run warmupCreateAccessCheck | Warm-up createAccessCheck: Listaro's runStage3, account-warmup.ts:181-209, recorded on 61594063207669 2026-10-22 (rule 35). | none | gotomarketplace → dwell → gotomarketplaceCreateItem → dwell → gotomarketplace → scroll |
flowWarmupFeedBrowse, flowWarmupFeedBrowseLive | log flow run warmupFeedBrowse | Warm-up feedBrowse: Listaro's runStage1, account-warmup.ts:92-130, recorded on 61594063207669 2026-10-22 (rule 35). | none | goto → dwell → scroll → gotomarketplace → dwell → scroll |
flowWarmupMarketplaceBrowse, flowWarmupMarketplaceBrowseLive | log flow run warmupMarketplaceBrowse | Warm-up marketplaceBrowse: Listaro's runStage2, account-warmup.ts:135-174, recorded on 61594063207669 2026-10-22 (rule 35). | none | gotomarketplace → dwell → scroll → openAny |
flowWarmupMessagesVisit, flowWarmupMessagesVisitLive | log flow run warmupMessagesVisit | Warm-up messagesVisit: Listaro's runStage4or5 (stage 5), account-warmup.ts:214-246, recorded on 61594063207669 2026-10-22 (rule 35). | none | gotomarketplace → scroll → gotomarketplaceYouSelling → dwell → gotomessages → dwell → scroll |
flowWarmupSellingVisit, flowWarmupSellingVisitLive | log flow run warmupSellingVisit | Warm-up sellingVisit: Listaro's runStage4or5 (stage 4), account-warmup.ts:214-246, recorded on 61594063207669 2026-10-22 (rule 35). | none | gotomarketplace → scroll → gotomarketplaceYouSelling → dwell |
Every command, CLI and MCP
Each command is a CLI command and, unless terminal-only, an MCP tool; the long-running ones write the live log. Grouped by platform: a platform's command is log <platform> <command> (rule 63). Saved flows are MCP tools that run log flow run.
Source: docs/diagrams/commands.mmd.
The hooks (rule 55)
One list (src/agentHooks/registry) writes Claude Code's .claude/settings.json and Codex's .codex/hooks.json. How to add one: skills/agentHooks/SKILL.md.
| Event | Matcher | Script | What it does |
|---|---|---|---|
| SessionStart | startup|resume|clear|compact | scripts/hooks/pageState.ts | where every open browser is, the moment a session starts or comes back from compaction |
| UserPromptSubmit | every turn | scripts/hooks/pageState.ts | where every open browser is, with each of matt's prompts |
| SessionStart | startup|resume|clear|compact | scripts/hooks/storage.ts | ~/.logCli/runs over 2 GB or the disk nearly full: said with log runs prune before AdsPower refuses to start |
| UserPromptSubmit | every turn | scripts/hooks/storage.ts | the same storage check with each of matt's prompts (measured at most every 15 minutes) |
| SessionStart | startup|resume|clear|compact | scripts/hooks/engines.ts | which commands run on the direct-API engine (from DIRECT_ROUTES) and each account's session state and governor use, so a session coming back from compaction knows the engines; and, loudly, when the log MCP server runs older code than the checkout, and which code files have uncommitted local changes (rule 106) |
| UserPromptSubmit | every turn | scripts/hooks/engines.ts | the same engine line with matt's prompt, only when an account's session state, its governor use or the MCP server's freshness changed |
| SessionStart | startup|resume|clear|compact | scripts/hooks/knowledge.ts | the knowledge base is there and how to search it and save to it, with how many chunks Redis holds, so a session looks things up before it guesses (rule 93) |
| SessionStart | startup|resume|clear|compact | scripts/hooks/inbox.ts | anyone who wrote to a signed-in account's inbox since the last look (every platform's inbox, incl. X Chat, read in the background every 4 to 8 minutes, and what each account's live watcher pushed: X Chat's socket, Discord's gateway, Slack's websocket in every workspace, rule 123), said loudly so a reply is never missed; and, from each X account's local inbox store with no network, who wrote last and is waiting on a reply |
| UserPromptSubmit | every turn | scripts/hooks/inbox.ts | the same inbox check with each of matt's prompts: a poll starts when one is due, and new messages from the last poll are put in front of the agent |
| PostToolUse | Bash|PowerShell|Monitor|mcp__log__.* | scripts/hooks/inbox.ts | the same inbox check after a log tool or shell call, so a reply that lands mid-task is said at once |
| SessionStart | startup|resume|clear|compact | scripts/hooks/queue.ts | what every account's queue did since the last look (held, sent, cooling off, stopped), so a queue is never running out of the agent's sight (rule 114) |
| SessionStart | startup|resume|clear|compact | scripts/hooks/useCases.ts | the play this session loaded, read from its own transcript, put back with the step it reached when the session comes back from compaction or resumes, and the context layer service started (rule 158) |
| UserPromptSubmit | every turn | scripts/hooks/useCases.ts | the play matt's prompt asks for, matched against every use case's own "How the owner asks for it" phrases and title, named with getGuide to load it before anything is done (rule 158) |
| UserPromptSubmit | every turn | scripts/hooks/queue.ts | the same queue events with each of matt's prompts |
| PostToolUse | Bash|PowerShell|Monitor|mcp__log__.* | scripts/hooks/queue.ts | the same queue events after a log tool or shell call, so a held item or a cool-off is acted on at once |
| SessionStart | startup|resume|clear|compact | scripts/hooks/xLive.ts | what X's own client shows on each X account right now (its unread notifications, DMs and X Chat, read through the account's one session the way x.com polls it), and every new like, retweet, reply, quote, mention or follow since this Claude session last looked (rule 115) |
| UserPromptSubmit | every turn | scripts/hooks/xLive.ts | the same X live state with each of matt's prompts, only what is new for this session |
| PostToolUse | Bash|PowerShell|Monitor|mcp__log__.* | scripts/hooks/xLive.ts | the same X live state after a log tool or shell call, so a reply on a post is answered mid-task |
| PreToolUse | mcp__log__(discord|slack|instagram).* | scripts/hooks/chatNames.ts | a Discord, Slack or Instagram call that names its chat by a bare id is refused and told to pass the name (a person, a group, or "<server> #<channel>"), so matt sees where every call goes (rule 154) |
| PreToolUse | Bash|PowerShell|Monitor | scripts/hooks/liveLogs.ts | a log-cli run that would hide its lines (piped through tail or head) is refused before it starts; a long one off a Monitor is told to start one (rule 26); any log action run by hand from a shell is refused and pointed at its MCP tool (rule 106) |
| PostToolUse | Bash|PowerShell|Monitor|mcp__log__.* | scripts/hooks/pageState.ts | a browser landed on a known state (checkpoint, captcha, prompt) or a step failed: its resolver, at once |
| Stop | every turn | scripts/hooks/pageState.ts | the turn does not end while a browser sits on a state log-cli can resolve (rule 54) |
Around every turn, whatever is called: SessionStart (pageState.ts, storage.ts, engines.ts, knowledge.ts, inbox.ts, queue.ts, useCases.ts, xLive.ts), UserPromptSubmit (pageState.ts, storage.ts, engines.ts, inbox.ts, useCases.ts, queue.ts, xLive.ts), Stop (pageState.ts).
The hooks in one turn
Written from AGENT_HOOKS (src/agentHooks/registry); the same list gives Claude Code's .claude/settings.json and Codex's .codex/hooks.json.
Source: docs/diagrams/hooks.mmd.
Known page states (rule 54)
From PAGE_STATES (src/browser/flow/helpers/pageState.ts), first match wins. The hooks report each with its resolver; only a person's state stops the account.
Source: docs/diagrams/pageStates.mmd.
Each command's usage
log launch
Start here: a guided menu to set log up (platforms, sign-ins, the account's browser and extensions), add an account, sign it in, check it and connect your agent (and Facebook Marketplace when Twenty is set up) Terminal only: prompts in a terminal (Clack). Call the commands it wraps directly.
log launch [--setup] [--json]log facebook listings
List the Twenty listings, by status, account or campaign MCP tools: listings, one per form, with named fields.
log facebook listings [--status <STATUS>] [--account <accountName>] [--campaign <campaignId>] [--json]log facebook listing
Show one Twenty listing, or create one for an account from a JSON file MCP tools: listing, listingCreate, listingRemove, listingRemoveLive, listingSweep, listingLint, one per form, with named fields.
log facebook listing <agencyListingId> [--json]
log facebook listing create --account <accountName> --file <listing.json> [--json]
log facebook listing sweep [--apply] [--json] (every listing whose account is blocked (checkpoint, restricted, banned, dead, deactivated, or a wrong password) is marked ACCOUNT_LOST, and a campaign's copy goes to another account; a dry run unless --apply)
log facebook listing lint --file <listing.json> [--json] (the copy rules on the file's copy versions, and whether it would publish; nothing is saved)
log facebook listing remove <agencyListingId|facebookListingId> [--live] [--bypass] [--now] [--json] (delete it on Facebook through the recorded flow marketplaceListingDelete, then REMOVED in Twenty; a dry run unless --live)
The file: title, description, price, priceCurrency, category, condition (new|usedLikeNew|usedGood|usedFair), photos (paths or URLs), locationQuery ('City, State' with the state written out, 'Bethlehem, Pennsylvania', or 'City, Country': an abbreviation, zip or number is refused, rule 72), locationCity, locationCountry, locationPostalCode (kept, never typed), copyVersions ([{ id, title, description }], written by skills/listingCopy; the first is the ad's own title and description; each copy of the ad picks the one least like what is live in its city).log facebook accounts
List the FACEBOOK accounts in Twenty: status, home, proxy, and whether each may run MCP tools: accounts, one per form, with named fields.
log facebook accounts [--json]log facebook account
Show one account, or set its status (banned/dead/restricted replace its campaign listings) MCP tools: accountShow, accountResave, accountResetOpens, accountStatus, one per form, with named fields.
log facebook account show <accountName> [--json]
log facebook account resave <accountName> --from <folder> (a run's device and session Twenty didn't take: ~/.logCli/runs/unsaved/...)
log facebook account resetOpens <accountName|all> [--reason <text>] (today's launches and host opens count from now; nothing is deleted)
log facebook account status <accountName> <ACTIVE|WARMING|LIMITED|PAUSED|NEEDS_LOGIN|CHECKPOINT|RESTRICTED|BANNED|DEAD|DEACTIVATED> [--reason <code>] [--detail <text>]log adsPower
AdsPower: Local API status, its profiles, and one profile per Twenty account (sync) MCP tool: adsPower (args takes the same arguments).
log adsPower status
log adsPower profiles [--json]
log adsPower sync [--apply] [--account <accountName>] [--homes <US,IE>] [--json] (dry run unless --apply; a DEACTIVATED account, or one whose home isn't in --homes or LOG_ADSPOWER_HOMES, gives its profile back first)log localAccount
Accounts kept in a local file instead of Twenty: add one, with the account's browser profile, pull every Twenty account in, or list them MCP tool: localAccount (args takes the same arguments).
log localAccount add --platform <facebook|twitter|reddit|discord|linkedin|slack> --account <accountName> [--adsPower <profileId>] [--proxy <scheme://user:pass@host:port>] [--home <CC>] [--city <city>]
log localAccount list [--json]
log localAccount pull [--platform <p>] [--refresh] [--json] (copies every account Twenty has, cookies and login included, into the file; an account already there is kept unless --refresh; each keeps its Twenty id, so listings and groups still find it)
A local account lives in ~/.logCli/localAccounts.json (LOG_LOCAL_ACCOUNTS moves it), not in Twenty. Every command finds it by its accountName, a local row winning over a Twenty row of the same name, and the session a run captures is saved back to the file. LOG_ACCOUNTS=local leaves Twenty out of accounts entirely.log facebook lifecycle
Each account's place in its lifecycle (rule 35): status, state, what onboarding has left; states prints the machine MCP tool: lifecycle (args takes the same arguments).
log facebook lifecycle [--account <name>] [--json]
log facebook lifecycle states [--write] (the machine's state tree; --write regenerates docs/lifecycle.md)log facebook preflight
Before any browser opens, check each account will get through: home timezone, hours, proxy speed and country, the account's browser and its kernel, launches and host opens left, no session left open, its picture on disk MCP tool: preflight (args takes the same arguments).
log facebook preflight [--account <accountName>] [--homes <US,CA>] [--noProxy] [--apply] [--json]
Before any browser opens: each account's home timezone, waking hours, proxy (country and speed), AdsPower profile and browser kernel, today's launches and host opens, a session left open, and its profile picture on disk.
--apply downloads the SunBrowser kernel each profile's user agent needs when it is missing, and waits for it. Nothing opens on Facebook.log facebook doctor
Check python, facebook-camofox-client, Camoufox, Twenty, and which accounts may run MCP tool: doctor (args takes the same arguments).
log facebook doctor [--json]log facebook connect
Sign a Facebook account in through its proxy (the connect machine), save its cookies to Twenty MCP tool: connect (args takes the same arguments).
log facebook connect --account <accountName> [--noPrompt] [--wait <seconds>] [--bypass] [--now]log facebook profile
Set an account's profile picture and cover photo from Twenty (dry run unless --live, rule 28) MCP tool: profile, profileLive (args takes the same arguments).
log facebook profile setup --account <accountName> [--live] [--bypass] [--json]log facebook host
Run an account's lifecycle in ONE browser kept open for its day, resuming where it left (rule 35); send it events; read its progress MCP tools: hostStart, hostStartLive, hostSend, hostStatus, one per form, with named fields.
log facebook host start --account <accountName> [--live] [--likes] [--bypass] [--now] [--gapSeconds <n>] [--watchMinutes <n>] (--live: profile, name, location; --likes: the warm-up's likes)
log facebook host send --account <accountName> <PERSON_DONE|PAUSE|RESUME|SHUTDOWN|LOST>
log facebook host status [--account <accountName>]log inspect
Drive the account's browser, gated and headed, a step at a time: each step's snapshot, screenshot, HAR and rea, recorded as it happens (rule 29) MCP tools: inspectStart, inspectDo, inspectDoLive, inspectRead, inspectMap, inspectGoal, inspectFinding, inspectSessions, one per form, with named fields.
log inspect start --account <accountName> [--goal <text>] [--url <https://www.facebook.com/... or the platform's own page>] [--bypass] [--now] [--noIdleMouse] [--detach] (one session per account: two accounts can be open at once; --detach runs it as its own process and returns once it is open)
log inspect map [<runDir>] [--all] (the recorded steps as states and transitions: flow.md, machine.json; default the latest run; --all collates every run)
log inspect do <goto <url>|snapshot|back|wait [s]|click <ref>|type <ref> <text>|press <key>|upload <ref> <file>|highlight <role> <name>|solve|signIn|code|pin|stop> [--live] [--goal <text>] [--timeout <s>] [--json] [--account <accountName>] (--account picks the session when more than one is open; every step's internal API calls are read, and a step no known state names is read by SubQ against the goal, rules 73-74)
log inspect read <stepFolder> --account <accountName> [--goal <text>] [--json] (read a recorded step again: its internal API calls, the page and the earlier steps, by SubQ when no known state names it)
log inspect goal [--set <text>] [--account <accountName>] [--json] (the session's goal, whether a step's reading says it is reached, and its P.E.E. findings: what went wrong, the evidence, why, and how it was fixed; rule 75)
log inspect finding --step <step> --point <text> --explain <text> [--evidence <text>] [--account <accountName>] (a step that went wrong: Point, the issue in one sentence; Evidence, quoted from the step's reading, plus your own; Explain, why the evidence shows it and what caused it)
log inspect finding --fixes <n> --fix <text> [--account <accountName>] (how finding n was fixed)
log inspect sessions (the open sessions)log flow
Recorded inspect runs as flows: docs, an XState machine per flow, run on any account (rule 31) MCP tool: flow, flowLive (args takes the same arguments).
log flow fromInspect <runDir> --name <name> [--summary <text>] [--rename text1=firstName]...
log flow docs <name> | list | show <name>
log flow run <name> --account <accountName> [--set <param>=<value>]... [--live] [--bypass] [--now] [--json]log <platform> warmup
The warm-up, by platform and activity: log facebook warmup reel|feed|stages (the account's browser primed with the activity, each step checked, the outline written; rules 49, 60) MCP tool: warmup, warmupLive (args takes the same arguments).
log <platform> warmup <activity> [--account <a>] [--live] [--likes] [--bypass] [--now] (record: the account's browser opened primed with the activity, each step checked, the outline written)
log <platform> warmup <activity> plan [--json] (the activity's steps and what each must do)
log <platform> warmup persona --account <a> [--newPersona] [--seed <n>] (the account's behaviour profile and real viewer, from the datasets; no browser, no SubQ)
log facebook warmup reel plan [--reels 8-20] [--watch 4-25] [--skip 0.6] [--comments 0.3] [--profile 0.15] [--likes] [--seed <n>] [--json] (a drawn Reels session)
log facebook warmup reel run --account <a> [reel plan flags] [--live] [--likes] [--bypass] (a drawn session, stopped at the first step that doesn't do what it should)
log facebook warmup reel run --account <a> --persona [--newPersona] [--reels 8-20] [--trafficMb 1024] [--live] [--likes] [--bypass] (the persona's For you feed: SubQ reads each reel, the orderer plans it from the pack and real viewer, rule 50)
log facebook warmup feed run --account <a> --persona [--posts 8-15] [--trafficMb 1024] [--live] [--likes] [--bypass] (the home feed post by post: SubQ reads each post, a glance or a read and a like where the persona would, then Marketplace and listings)
Platforms: facebook. Facebook activities: reel, feed, stages.
log facebook warmup stages plan
log facebook warmup stages record --account <accountName> [--only feedBrowse,marketplaceBrowse] [--likes] [--bypass] [--now]
log facebook warmup stages run --account <accountName> [--only …] [--likes] [--gapSeconds <n>] [--bypass] [--now]
log facebook warmup stages log [--account <accountName>] [--lines <n>] [--follow [--seconds <n>]]log facebook join
Join a Facebook group as one account, in its open browser: read the gate, --live presses Join; every step patches agencyGroups.joinSubmission (rule 45) MCP tool: join, joinLive (args takes the same arguments).
log facebook join group --account <accountName> --group <group url, id or name> [--live] [--json]
log facebook join group --account <accountName> --group <g> --show
log facebook join group --account <accountName> --group <g> --answer [--set <n>=<value>]... [--agree] (Clack steps over the group's questions, or the same answers given here; saved to Twenty, never sent)
log facebook join group --account <accountName> --group <g> --fill [--live [--submit]] (the draft into the group's dialog, step by step)
Every step in the browser runs inside the account's local hours (rule 18); --bypass skips that for one run, only when matt asked (rule 20).
The account's browser must be open (log inspect start --account <a>, or log facebook host start): a join is a step in it (rule 35).log facebook read
Read a Facebook group's new posts and their comments, or its newest members, as one account, by hand: posts on the direct-API engine first, else in its open browser; only the group's cursor is saved to Twenty (#27, rule 65), the members to a file MCP tools: readGroup, readGroupLive, readMembers, one per form, with named fields.
log facebook read group --account <accountName> --group <group url or id> [--scrolls <n>] [--commentPosts <n>] [--live] [--bypass] [--json]
log facebook read group --account <accountName> --group <g> --show [--json]
Reads the group's new posts (since its cursor in Twenty) and the comments of the new posts that have some. Only the cursor is saved to Twenty (rule 65); --show prints it.
Without --live, on a numeric group id, it runs on the direct-API engine first (fbk groups.feed_read, no browser) when the account has a saved session and a proxy in its home country: Facebook's own page decides, not accountStatus (loggedIn reads; checkpoint stops and deactivates; anything else hands over to the browser). The result goes to socialAccounts.sessionState.
--scrolls: feed scrolls at most (default 6). --commentPosts: posts whose comments are read at most (default 5; 0 reads none).
--worker (OpenMagpie's call) is refused here: it became its own command, log facebook poll (its usage).
--live: each post's comments are switched to Newest first (two clicks, rule 8); without it nothing is clicked and the page's Most relevant comments are read.
Every step in the browser runs inside the account's local hours (rule 18); --bypass skips that for one run, only when matt asked (rule 20).
On the browser path the account's browser must be open (log inspect start --account <a>, or log facebook host start): a read is a step in it (rule 35).
log facebook read members --account <accountName> --group <group url or id> --count <n> [--secondsPerScroll <s>] [--maxScrolls <n>] [--owners] [--bypass] [--json]
Reads the group's newest members from its People tab (/groups/<g>/members) in the account's open browser: the page's first 10, then a scroll at a time, each scroll's GroupsCometMembersPageNewMembersSectionRefetchQuery answers (about 10 members each, 2 a scroll). Nothing is clicked (rule 8).
--count: members to read (1-5000). --secondsPerScroll: the wait before each scroll (default 15; matt: the time 40 scrolls take is a variable). --maxScrolls: scrolls at most (default twice the plan's, plus 2). It says up front: count N → ~S scrolls, ~M min at Xs per scroll.
It stops at --count members, when the list ends (has_next_page false) or at --maxScrolls. --owners keeps only owners: a bio naming owner, co-owner, founder, CEO, president, proprietor or self-employed, or a page (a category set).
The members go to ~/.logCli/members/<group>/<run time>.tsv and .json, never to Twenty; it prints how many, how many with a bio, owners and pages, and the files. --json gives them.log facebook poll
Poll a Facebook group from one account, in its open browser, for a time or once: each poll's posts as one JSON line, OpenMagpie's records; --request answers OpenMagpie's facebook_group worker (#27, rule 63) MCP tool: poll (args takes the same arguments).
log facebook poll <group url or id> <accountName> [<how long to poll: 30m, 2h>] [--bypass]
log facebook poll --request <request json> (OpenMagpie's facebook_group worker: groups.search in, post records out as one JSON line; python/openMagpie/facebookWorker.py calls it)
One poll reads the group once in the account's open browser, posts only, nothing clicked, and prints one JSON line: the posts it saw, as OpenMagpie's records. With a time it polls every 5 minutes until the time is up, a line each.
Every poll runs inside the account's local hours (rule 18); --bypass skips that, only when matt asked (rule 20). --request never skips it.
The account's browser must be open (log inspect start for that account, or log facebook host start): a poll is a step in it (rule 35).log facebook check
Check one account in its open browser: may it still sell, each listing's state on Selling (into Twenty), and its seller inbox's chats MCP tools: check, one per form, with named fields.
log facebook check --account <accountName> [--bypass] [--json] (in the account's open browser: Marketplace status, each listing's state and clicks in the last 14 days on Selling into Twenty, the seller inbox's chats; nothing clicked)log facebook scan
Read Marketplace > Selling live for one account and update its listings in Twenty MCP tool: scan (args takes the same arguments).
log facebook scan --account <accountName> [--json] [--bypass]log facebook status
Open one Marketplace item page live and classify its status MCP tool: status (args takes the same arguments).
log facebook status <listingId> --account <accountName> [--json] [--bypass]log facebook watch
Run the listing monitor (XState) for one account and print every step MCP tool: watch (args takes the same arguments).
log facebook watch --account <accountName> [--checks <n>] [--interval <minutes>] [--json] [--bypass]log proxy
Check an account's proxy, give it one from a provider (2Captcha, Byteful) in its home city, or see providers' traffic MCP tool: proxy (args takes the same arguments).
log proxy check --account <accountName> [--json]
log proxy assign --account <accountName> | --missing [--vendor twoCaptcha|byteful] [--city <city> [--region <state>] | --anyCity] [--minutes <1-120>] [--socks5]
log proxy balance [--json]
log proxy static [--json] (Byteful's static ISP IPs on the account, where each is)
The proxy is the socialAccount's `proxy` field in Twenty (scheme://user:pass@host:port), its country `homeCountry`.log facebook publish
Post one Twenty listing (dry run unless --live), printing every machine step MCP tools: publish, publishLive, one per form, with named fields.
log facebook publish --listing <agencyListingId> [--live] [--json] [--formUrl <url>] [--bypass] [--now]
Dry run unless --live: fills and checks everything, never clicks Publish.
The listing, its account and the account's proxy all come from Twenty.
--bypass skips only the account's waking hours (rule 18); every other gate still runs.
--now skips only the 10-minute launch gap (rule 24); the 8 launches a day still hold.log facebook campaign
Put one ad on N accounts in one place, replace lost accounts, publish the campaign MCP tool: campaign, campaignLive (args takes the same arguments).
log facebook campaign fill --campaign <agencyCampaignId> --from <agencyListingId> --accounts <n>
log facebook campaign heal --campaign <agencyCampaignId>
log facebook campaign run --campaign <agencyCampaignId> [--live] [--bypass]
log facebook campaign show --campaign <agencyCampaignId> [--json]log facebook conversations
List buyers' Marketplace conversations in Twenty, newest first, by account or listing MCP tools: conversations, one per form, with named fields.
log facebook conversations [--account <name>] [--listing <agencyListingId>] [--json]log facebook conversation
One conversation: show its messages, mark read, set its lead status, link its listing, or store a scanned thread MCP tools: conversationShow, conversationRead, conversationLead, conversationLink, conversationRecord, one per form, with named fields.
log facebook conversation show <id> [--json]
log facebook conversation read <id>
log facebook conversation lead <id> <NEW|CONTACTED|QUALIFIED|BOOKED|LOST|SPAM>
log facebook conversation link <id> --listing <agencyListingId>
log facebook conversation record --account <name> --file <threads.json>log facebook messenger
Messenger's PIN for the account's encrypted chats: asked in a Clack prompt, kept in Twenty, never printed Terminal only: asks matt for the Messenger PIN in a Clack prompt (masked). He runs log facebook messenger pin --account <a> in a terminal.
log facebook messenger pin --account <accountName> [--show] (a Clack prompt asks for the 6-digit PIN twice, masked, and keeps it in Twenty; --show says only whether one is set)log errors
List every error code: meaning, retryable, where each machine ends up MCP tools: errors, one per form, with named fields.
log errors [--layer <account|listing|photo|form|captcha|runtime|twenty|proxyVendor|auth|profile>] [--code <code>] [--json] [--markdown]log graph
Print a machine's states and transitions as a Mermaid diagram MCP tool: graph (args takes the same arguments).
log graph <publish|monitor>log facebook capture
Read a run's raw HAR (any browser driver) and its scripts with offline rea; --docs builds every platform's client docs, rea reading each request's code. No browser. MCP tool: capture (args takes the same arguments).
log facebook capture --har <path> [--out <dir>] [--noRea] [--json]
log facebook capture --settle <runCaptureDir> (what each run starts in the background: rea reads its capture, split per step)
log facebook capture --docs [--noRea] (every run's requests → docs/platforms/<platform>/, one doc per request, rea reading each one's slice: Facebook's by its modules, every other platform with a browser path by the call sites its bundles hold for each request)
The HAR is the one Playwright writes from the account browser that ran. It is copied raw, and rea reads it. No browser opens.log runs
The recorded runs on this machine, and pruning the ones rea settled and the request docs hold, keeping each one's findings and domains MCP tools: runsList, runsExchanges, runsPrune, runsSpace, one per form, with named fields.
log runs [list] [--json] (every recorded run: its size, settled by rea, in the request docs, and why it is kept or that it may go)
log runs exchanges (takes every recorded step's internal API calls, what was sent and what came back, out of its HAR into exchanges.json, for every run still on disk; prune does it first too)
log runs prune [--before <YYYY-MM-DD>] [--apply] (deletes each analysed run, started before the day when given, after taking out every step's exchanges, keeping its findings.md, steps.jsonl, settle.json, every step's exchanges.json, reading.json and readingState.json, its goalFindings and its requests by domain; a dry run unless --apply)
log runs space (the size of ~/.logCli/runs and the disk's free space, as the storage hook says them)log timing
Where the time goes in every call: p50, p95 and p99 per tool and per stage, from the timings every call stores in log.db, or one call as a waterfall MCP tools: timing, one per form, with named fields.
log timing [--tool <name>] [--hours <n>] [--stages <n>] [--json] (p50, p95 and p99 for every tool call, and for every stage inside it: the guards, the voice gate, each fetch, the Python start, the session host's queue, lock, spacing and governor, each platform request; newest --hours, default 24)
log timing --call <callId> [--json] (one call as a waterfall: every stage in order, when it started and how long it took; the id is on the call's ▶ and ■ lines in the live log)log inbox
Claim a chat before acting on it, release it, list the live claims, and read the log of every action taken in a chat MCP tools: inboxClaim, inboxRelease, inboxResume, inboxClaims, inboxActions, one per form, with named fields.
log inbox claim --platform <twitter|discord|slack|instagram> --account <a> --chat <conversationId|handle> [--event <eventId>] [--json]
log inbox release --platform <p> --account <a> --chat <conversationId|handle> [--json]
log inbox claims [--json]
log inbox resume --platform <p> --account <a> --by matt [--json]
log inbox actions [--platform <p>] [--account <a>] [--chat <conversationId|handle>] [--limit <n>] [--json]
Only the session that claims a chat acts on it (rule 133). Every message the push channels bring in has an event id (shown in >>> NEW MESSAGE). claim takes the chat for this session for 10 minutes before you draft or send; a second session is refused (chatClaimedElsewhere). Every send claims the chat itself and releases it once it is sent, interrupted or failed, so two sessions never answer the same chat. A claim held by a session that ended frees itself. resume is matt's go-ahead after a refused catch-up (rule 94): the account's gap opens again and its owner catches up through its own session within 30 s; only when matt says so. claims lists who holds what; actions is the log of every claim, refusal, send and release, oldest first, to search what was already done in a chat before acting.log media
Describe every image, meme and video with one local vision model into the .db, so replies read what was sent MCP tools: mediaDescriber, mediaDescribe, mediaStatus, one per form, with named fields.
log media describer [--json]
log media describe <path> [--wait <seconds>] [--json]
log media status [--json]
Every image, meme, gif and video sent or received is described by one local vision model (onnx-community/LFM2.5-VL-450M-ONNX, q4, on this machine's transformers.js) and the description, labelled with what the media is, is stored in the .db with its jina-embeddings-v5-omni vector, so the voice step reads what was sent before anyone replies (rule 138). describer is the one warm process per machine (the session hook starts it; a second is refused): it loads the model once, takes one item at a time from a queue of at most 20 (the overflow is marked not described yet and shown so in every context), describes a video from three frames, beats every 5 s, and stops loudly past its memory ceiling. describe puts one local file in the queue and waits for its description. status shows the queue. Audio goes to AssemblyAI (rule 126), never here.log browsers
Where every open browser is: its page, the known page state it sits on, the call that resolves it, and what only matt can clear (rules 54-55) MCP tools: browsers, one per form, with named fields.
log browsers [--json] (every open browser: its step and page, the known page state it sits on, the call that resolves it, and what only matt can clear)log job
Follow or stop a long MCP run: each runs as a job when it outlasts its call's wait MCP tools: jobStatus, jobCancel, jobList, one per form, with named fields.
log job status <jobId> [--afterLine <n>] [--waitMs <ms>] [--json] (a long MCP run: its status, the lines after afterLine, and its result once it ends; waits up to waitMs, default 10000, for something new)
log job cancel <jobId> (stops it at its next browser step; never once Publish is clicked, rule 10)
log job list [--json] (the latest jobs)log mcp
This server's MCP sign-ins over OAuth (through Twenty, the owner only): list the clients, revoke them Terminal only: manages who may sign in to this server's MCP (OAuth through Twenty). matt runs log mcp clients or mcp revoke <clientId|all> in a terminal on the server.
log mcp clients [--json] (who signed in to this server's MCP over OAuth: each client, when it was last used, its live tokens, and whether this server has an owner)
log mcp revoke <clientId|all> (the client's tokens stop working at once; it must sign in through Twenty again)log login
Only for a team on Twenty: sign in to its Twenty through its sign-in app (OAuth). Local accounts need no sign-in, and without Twenty set up it says so Terminal only: opens a browser for Twenty OAuth and waits for it. Run log login in a terminal.
log login [--webUrl <url>] [--apiUrl <url>] [--token <accessToken>] [--json]log logout
Forget the stored Twenty session MCP tool: logout (args takes the same arguments).
log logout [--apiUrl <url>] [--json]log whoami
Show who is signed in, refreshing the session if it expired MCP tool: whoami (args takes the same arguments).
log whoami [--apiUrl <url>] [--json]log twitter session
Check that X still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty MCP tools: twitterSessionCheck, twitterSessionWebClient, one per form, with named fields.
log twitter session check --account <accountName> [--json]
log twitter session webClient --account <accountName> [--json] (loads x.com/home through the account's one X session, as twikit already does to sign its calls, and saves the page and every script it lists from abs.twimg.com to ~/.logCli/twitter/<account>/webClient/<time>, reads spaced 0.8 to 2.5 s, so rea can read X's own signed-in client: what its composer sends; rule 113)
Checks that X still accepts the account's saved session, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80). Sends nothing.
X's own answer decides: the session check's HTTP status (401 or 403 loggedOut, 200 or 404 loggedIn), then the account's profile read (suspended or locked: restricted). The result goes to socialAccounts.sessionState, and X's refreshed cookies are merged back into the row.log twitter follows
Read whether handles follow an X account and it follows them, on the direct-API engine, sending nothing MCP tools: twitterFollows, one per form, with named fields.
log twitter follows --account <accountName> --handles <handle,handle…> [--json]
Reads, for each handle, whether it follows the account and whether the account follows it, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80). Sends nothing.
X's own answer decides: one friendships/show read per handle, 1.5 s apart, after the session check. Handles come from treg research or matt, never a guess (rule 53).log twitter dms
Read an X account's direct messages and who is waiting on a reply, on the direct-API engine, sending nothing MCP tools: twitterDms, one per form, with named fields.
log twitter dms --account <accountName> [--limit <n>] [--waiting] [--with <handle>] [--conversation <id>] [--full] [--force] [--json]
Reads the account's X direct messages on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80). Sends nothing and marks nothing read.
Without --conversation it syncs the inbox the way X's own chat app does (dm-core): the first read takes the whole X Chat inbox (GetInitialXChatPageQuery, then GetInboxPageRequestQuery pages) and the old DM inbox (dm/inbox_initial_state, then dm/inbox_timeline pages, 1.5 s apart) and keeps them in ~/.logCli/twitter/<account>/inboxSync.json with the newest event's sequence id; every read after that asks X only for what is newer (GetMessageEventsPageQuery from that sequence id, beside one dm/inbox_initial_state), unlocks the PIN and decrypts only when something is new, opening just those conversations' keys (GetInboxPageConversationDataQuery), and keeps each event once by its id. --full reads everything again. Each read saves its conversations for log search and updates the search index in the background with only the chunks that changed. It then lists the newest conversations first, one row per person (their old DM thread and X Chat thread merged): who, when, the last message from either and whether it was yours. --waiting keeps only the ones whose last message isn't yours, the people waiting on a reply, and lists apart the ones that opened your last message, by reacting to it or reading it without replying (read receipts, where the person keeps them on): so each is a follow-up to send, not a reply they're waiting on (--json: followUps). --conversation <id> reads one conversation's messages, oldest first: a legacy id (two user ids joined by a dash) through dm/conversation, an X Chat id (joined by a colon) the same way --with reads it, never the whole inbox.
--with <handle> reads everything with one person by their handle and nobody else: their user id, their legacy thread (dm/conversation) and their X Chat thread (read the way X's own chat app opens a chat, from its dm-core: GetInboxPageConversationDataQuery for that one conversation, then GetConversationPageQuery from the oldest message held, its fetchOlderMessages, up to 5 pages 1.5 s apart while X says there is more), merged into one timeline, oldest first, a message mirrored into both shown once. It never reads the whole inbox. A 429 stops the read with nothing retried, as X's app does (it retries only 5xx), and pauses the account's governor (rule 94). X Chat (encrypted DMs) is opened with the account's X Chat PIN from Twenty (`log twitter chat pin`), recovered through Juicebox by the vendored chat-xdk; without a PIN its text stays encrypted. An old DM thread (<a>-<b>) and its X Chat thread (<a>:<b>) with one person are one conversation: X shows them in the same place, and a dm sent through the old DM API shows in their X Chat too. A sent answer is delivered whichever conversation id it names: never resend it and never call it the wrong inbox. X's own answer decides: the session check first, then the inbox's answer. --limit is how many conversations (or messages) to show, default 20, at most 200.log twitter chat
X Chat's PIN for the account's encrypted DMs: asked in a Clack prompt, kept in Twenty, never printed Terminal only: asks matt for the X Chat PIN in a Clack prompt (masked). He runs log twitter chat pin --account <a> in a terminal.
log twitter chat pin --account <accountName> [--show]
log twitter chat watch --account <accountName> [--seconds <n>] [--inbox] [--json]
Saves the account's X Chat PIN (the 4-digit code X asks for to open encrypted DMs) to Twenty, asked twice in a masked Clack prompt and never printed or passed as a flag. It is checked first: the direct-API engine (no browser, no proxy, rule 80) unlocks the account's keys with it through Juicebox (a wrong PIN uses one of X's limited guesses), and only a PIN that opens them is saved. `log twitter dms` uses it to recover the account's X Chat keys through Juicebox (vendor/chatXdk) and read the encrypted messages. --show says only whether one is set.
watch keeps one socket open to X Chat, the way X's own chat app does (read with rea from X's shipped code, issue #68): a token from GenerateXChatTokenMutation, wss://chat-ws.x.com/ws, a KeepAlive every 30 s, a fresh token and a new socket when it drops. X pushes each message the moment it lands, so nothing is polled and no inbox read can hit X's 429. Each message is decrypted with the saved PIN and printed as it arrives (who, when, what they said). --inbox also hands each new message from someone else to the inbox hook, which puts it in front of the agent with what to do next. --seconds stops it after that long; without it, it watches until stopped.log twitter cookies
Add or refresh an X account in Twenty: its session cookies from a masked prompt, checked against X, and its copy style Terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal.
log twitter cookies save --account <handle> [--style <copyStyle>] [--name <label>]
Saves an X account's session to Twenty from a masked Clack prompt: its auth_token, ct0 and twid cookies (twid carries the account's id), copied from a browser signed in to x.com, never passed as flags or printed. They are checked first: the direct-API engine (no browser, no proxy, rule 80) pings X with them, and only cookies X accepts for that same handle are saved. A handle Twenty doesn't have yet gets a new TWITTER socialAccounts row (rule 13); one it has gets its cookies replaced. --style sets the account's copyStyle, the writing style every send and queue check holds its copy to (empty: house). The account's governor starts under ~/.logCli/twitter/<handle>.log voice
Gate a draft against matt's own writing guide with SubQ before anything is sent as him, sending nothing MCP tools: voice, voiceAiIndex, voiceIndex, one per form, with named fields.
log voice --to <handle> [--text "<draft>"] [--context "<what they said>"] [--with <person or chat>] [--spoken] [--json]
log voice aiIndex [--json] (builds the AI tells index: every literal phrase in vendor/aiTells, tbhb/vale-ai-tells first and then the other lists, embedded with Jina (JINA_API_KEY), with matt's own replies from the voice index measured against it for his p50 and p99 closeness, and the vale rules he uses himself muted; the gate builds it itself when it is missing or older than the voice index; rule 112)
log voice index [--json] (builds matt's voice from every conversation log search already saved, reading nothing new: each of his replies paired with what was said just before it, embedded with log search's own model, and a profile per chat, person and platform counted from his own messages)
Gates a draft before anything is sent as matt (his rule, 2026-10-27): SubQ reads it against matt's own writing guide (GUIDES/MATT_WRITING_STYLE.md, built from 256 of his tweets) and the dms he wrote himself, and answers whether it sounds like he typed it (voice, 0.8 to pass), whether it answers what they said (--context) or something specific about them, whether it reads like a mate texting, and, to someone matt has never talked with, whether it is only a relevant opener with no ask or pitch (openerOnly, matt 2026-10-31). A phrase matt never says (hit home, big swing, spot on, felt that…) fails it outright. Sends nothing.
--with names the chat or person the way matt says it ("the log group", "jilly", a handle): log finds matt's real replies to things like --context, from that chat first, then that person, then that platform, then anywhere, and how he writes there (length, case, punctuation, his words). Without --text it shows them to draft from; with --text the gate also asks whether the draft reads like his real replies there (toThem, 0.7 to pass). Every gate also reads his nearest real replies, closest wording and style across all his conversations, every platform, beside the chat's own, never the writing guide alone.
--spoken gates a voice note: it is graded as said out loud, so its length and punctuation never count against it, and it must build on what they are talking about across the whole chat, adding to it, never repeating what matt already told them (buildsOn, 0.7 to pass, rule 149). Every voice note send is gated this way.
Exit 0 when it passes, 1 when it doesn't: rewrite and gate again.log search
Hybrid search across every conversation (BM25 keyword + vector meaning, fused with RRF), and building its indexes MCP tools: search, searchIndex, searchBench, one per form, with named fields.
log search "<query>" [--limit <n>] [--platform <p>] [--account <a>] [--with <handle>] [--after <YYYY-MM-DD>] [--json]
log search index [--platform <twitter|discord>] [--account <a>] [--saved] [--rebuild] (only the chunks that are new or changed are embedded, and the ones gone are removed, so the index stays deduplicated; --rebuild embeds everything again. --saved indexes the conversations already kept, reading nothing; otherwise reads every conversation through the platform's engine: X's DMs with X Chat decrypted with the saved PIN, and every Discord DM and group (its whole history, up to 20,000 messages each), keeps them in ~/.logCli/conversations, and builds the keyword and meaning indexes in ~/.logCli/search)
log search bench [--rounds <n>] (times realistic queries on the built index, cold and warm, and prints p50, p95 and p99 beside a plain linear scan)
Hybrid search across every conversation: BM25 keyword ranking (bm25s) and meaning ranking (verso-db HNSW over local all-MiniLM-L6-v2 embeddings) run in parallel and are fused with reciprocal rank fusion. Each hit names the conversation, who it was with, when, and the messages it covers.log kb
The knowledge base: search it, sync every source into Twenty and Redis, remember a note, show an article MCP tools: kbSearch, kbSync, kbRemember, kbShow, one per form, with named fields.
log kb search "<query>" [--kind <kind>] [--limit <n>] [--json]
log kb sync [--noAccounts] [--json] (collects AGENTS.md rules and sections, skills, docs, infra notes, every Claude memory file, the known page states and the accounts from log-cli's Twenty, writes them to the knowledge base in ListeningKit's Twenty, and indexes what changed into Redis)
log kb remember "<text>" [--title <t>] [--tags <a,b>] [--json] (saves an agent memory as a note in Twenty and Redis at once)
log kb show <key> [--json] (one article in full, e.g. rule:73, skill:inspect, pageState:messengerPinCreate)
Kinds: rule, section, skill, doc, infra, memory, pageState, account, note. Twenty (node02, over the tailnet) holds every article and can be edited there; Redis 8 holds the search index: BM25 keyword and HNSW meaning (local all-MiniLM-L6-v2) fused with reciprocal rank fusion. Secrets are scrubbed before anything is written.log memelord
The Memelord extension: search memes for a moment, read one template, see what is trending, list the image tools (free tools only) MCP tools: memelordSearch, memelordTemplate, memelordTrending, memelordTools, memelordTaste, one per form, with named fields.
log memelord search "<what the moment is>" [--type meme|video|sticker] [--method context|combined|media|fulltext|fuzzy] [--limit <n>] [--json]
log memelord template <id> [--json] (one template: its name, media url, text slots and what it means)
log memelord trending [--limit <n>] [--media image|video] [--json] (what is trending now, with its cultural context)
log memelord tools [--category imageToImage|imageTextToImage|textToImage] [--json] (the catalog of one-shot image tools; log never runs them)
log memelord taste [--build --from <posts.json>] [--json] (matt's meme taste: the styles he named and the memes he has sent, each read by SubQ from its picture; --build reads a file of his X media posts)
Search ranks what it finds by matt's taste (--noTaste turns it off): SubQ reads each template's picture against the styles he named (deep fried, larping, streamer reacts like FlightReacts, Zias and Bloo, shitposts) and the memes he has sent, and the taste line leads the answer.
Memelord is an extension (docs/extensions/memelord.md): only its four free discovery tools are reachable from log, so nothing here spends money (rule 41). The key is MEMELORD_API_KEY in .env.local. A meme goes out on Discord as its media url on a line of its own, which Discord shows inline.log treg
The treg extension: find an endpoint by the job, read it, and call it with the key injected server-side (free ones run, priced ones only when matt asked) MCP tools: tregSearch, tregGet, tregCall, tregCallLive, one per form, with named fields.
log treg search "<what you want done>" [--limit <n>] [--json] (treg's catalog, found by the job: each endpoint's id, what it does and its price)
log treg get <endpointId> [--json] (one endpoint in full: its price, its params and a call that works)
log treg call <endpointId> [--params '<json object>'] [--path <path>] [--method <GET|POST>] [--data '<body>'] [--await] [--approvedBy <who>] [--live] [--json] (calls it through treg, the key injected server-side: a free endpoint runs; a priced one is a dry run that says its price unless --live with --approvedBy)
treg is an extension (docs/extensions/treg.md): every lookup comes from an external source through it (rule 53), and nothing that costs money runs unless matt asked (rule 41). It runs the treg CLI on this machine (TREG_BIN moves it); treg's balance is in log balance.log balance
Every paid provider log calls, with what is left and which are out of credit MCP tools: balance, one per form, with named fields.
log balance [--provider <name>] [--json]
Every paid provider log calls, with what is left: subq, jina, inworld, fishAudio, assemblyAi, parallel, treg, twoCaptcha, byteful.
A provider with a balance API (Jina, Fish Audio, treg, 2Captcha, Byteful) is read from it. One without one (SubQ, Inworld, AssemblyAI, Parallel) shows its last answer to a real call, which log keeps for every call (~/.logCli/billing/answers.jsonl). A provider that answered it is out of credit shows outOfCredit, so a failing voice note or gate is seen as money, not a bug.
Reading balances spends nothing (rule 41). Topping up is matt's: never done from log.log clone
matt's voice, cloned once per provider (Inworld or Fish Audio) from his own voice notes, and words said in it to a local file MCP tools: cloneVoice, cloneSpeak, one per form, with named fields.
log clone voice [--provider inworld|fishAudio] [--from <file>[,<file>]] [--apply] [--replace] [--json] (matt's voice, cloned once per provider from his own voice notes, or from recordings of his own he names with --from, each one's words read from a .txt beside it; without --apply it lists the recordings it would use; --replace makes it again and keeps the old voice id under previous)
log clone speak --text "<words>" [--provider inworld|fishAudio] [--model <model>] [--format mp3|wav] [--json] (the words in his cloned voice, to a file under ~/.logCli/voiceNotes; nothing is played or sent)
Voice cloning is provider-agnostic (docs/extensions/voiceClone.md): the provider is --provider, else LOG_VOICE_PROVIDER in .env.local, else inworld. Each provider is an extension of its own (src/extensions/inworld, src/extensions/fishAudio) behind one interface (src/extensions/voiceClone). The clone is made only from the voice notes matt sent himself (fromMe in the conversation store or his ownMessage in the .db), the shortest first, within the provider's limit (Inworld 3 to 30 s, Fish 10 to 60 s). Each provider's voice id is kept in ~/.logCli/voice/clone.json. A clone speaks with the provider's preferred model (Inworld inworld-tts-2, which takes emotion tags), else its cheapest, unless --model says otherwise, at LOG_VOICE_SPEED (default 1.15). The clone is made in matt's accent: LOG_VOICE_ACCENT (default Irish) as its description and LOG_VOICE_LANGUAGE (default en-IE, en-GB when Inworld refuses it) as its language, with each sample's AssemblyAI transcript when ~/.logCli/transcripts has one; no accent instruction is sent with the text. Every spoken file is shaped like matt's own voice notes (his room tone from his notes, phone squash, mic drift, saturation; breaths on inworld-tts-2) and listened back with AssemblyAI: a lexicon word said wrong is refused spokenMismatch, an acronym not in the lexicon pronunciationUnknown. A voice note goes out only when matt asks for one: log discord voice, log instagram voice or log slack voice (rules 142, 143, 145, 146, 147).log transcribe
Transcribe every voice note in the saved X and Discord conversations with AssemblyAI, written into the conversation store for search, voice and SubQ MCP tools: transcribe, transcribeFile, one per form, with named fields.
log transcribe [--platform twitter|discord] [--account <a>] [--list] [--noIndex] [--json]
log transcribe file <path> [--json] (one audio file, transcribed and kept by its hash)
Transcribes every voice note in the saved conversations with AssemblyAI (upload, transcript, polled until done) and writes each into the conversation store as (voice note: "<what was said>") in place of its (audio: <name>) marker, so log search, voice and SubQ matrices see what was said. X voice notes come from the X Chat files the account's one session saved, and a missing one is fetched through that session (the conversation read again, its audio events reopened). Discord voice notes are found with Discord's own sound search, one chat at a time through log's Discord engine, and each file is fetched from its fresh attachment url there; any refusal stops it (rule 94). Each transcript is kept once by its file's hash in ~/.logCli/transcripts, so nothing is sent to AssemblyAI twice. The search index is then updated with what changed (--noIndex skips it).
--list only lists the voice notes the store holds and which have their file, calling nothing. The key is ASSEMBLYAI_API_KEY or ASSEMBLY_AI_KEY (the environment, .env.local, ~/.buzz/.env.local or ~/filmcraft/.env.local), never printed; without it the command refuses with assemblyAiKeyMissing.log twitter followers
Read an X account's own followers on the direct-API engine, sending nothing MCP tools: twitterFollowers, one per form, with named fields.
log twitter followers --account <accountName> [--limit <n>] [--json]
Reads the account's own followers on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80), newest first: handle, name, bio, location, follower and post counts, whether they take DMs. Sends nothing.
X's own answer decides: the session check first, then X's followers list, 100 a page, 1.5 s apart. --limit is how many to read, default 200, at most 4000.log twitter notifications
Read who liked, reposted, replied to, quoted or followed an X account, new and historical, kept locally, sending nothing MCP tools: twitterNotifications, one per form, with named fields.
log twitter notifications --account <accountName> [--type likes|reposts|replies|quotes|follows|all] [--since <date>] [--post <id|url>] [--full] [--pages <n>] [--stored] [--limit <n>] [--json]
Reads the account's X notifications on the direct-API engine through its one X session, the engine host (rule 105: no session of its own; no browser, no proxy, rule 80) and keeps them in ~/.logCli/twitter/<account>/notifications.json: one row per person per thing they did (like, repost, reply, quote, follow, mention), with which of the account's posts it was on and when. Sends nothing.
Without --full it reads only what is new: the newest page, then older pages until it meets rows already kept (the first run reads up to 5 pages). --full then pages back through the history from where the last --full stopped, as far as X gives, at most --pages pages a run (default 25). The read is one engine call (rule 104: one call per account at a time); its pages are 40 notifications, 4 to 11 s apart, each through the account's governor; a 429 or a governor stop ends the read and keeps what came.
--stored answers from what is kept, calling nothing. --type, --since, --post and --limit (default 50 shown, at most 5000) narrow what is shown; --json gives the rows, counts by kind and the people who engaged most.log twitter reply
Reply to one post on X with approved copy, on the direct-API engine; a dry run unless --live MCP tools: twitterReply, twitterReplyLive, one per form, with named fields.
log twitter reply --account <accountName> --tweet <postUrl> --text "<copy>" [--media <file>[,<file>]] [--approvedBy <who>] [--skipStyleCheck] [--force] [--live] [--json]
Reply to one post on X with approved copy, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target, the text and its house style are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
X's create answer decides: a tweet id is sent, an empty result is a drop (nothing posted, usually the author's reply limits). Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter quote
Quote one post on X with approved copy, on the direct-API engine; a dry run unless --live MCP tools: twitterQuote, twitterQuoteLive, one per form, with named fields.
log twitter quote --account <accountName> --tweet <postUrl> --text "<copy>" [--approvedBy <who>] [--skipStyleCheck] [--force] [--live] [--json]
Quote one post on X with approved copy, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target, the text and its house style are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
X's create answer decides: a tweet id is sent, an empty result is a drop. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter post
Make one original post on X with approved copy and optional media (a video, a gif or up to 4 images), on the direct-API engine; a dry run unless --live MCP tools: twitterPost, twitterPostLive, one per form, with named fields.
log twitter post --account <accountName> --text "<copy>" [--media <file>[,<file>]] [--approvedBy <who>] [--skipStyleCheck] [--force] [--live] [--json]
Make one original post on X with approved copy and optional media (a video, a gif or up to 4 images), on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target, the text and its house style are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
Each file is uploaded first (a video in chunks, then X's own processing status must say succeeded before the post is made), then the post is created with the media ids. X's create answer decides: a tweet id is sent, an empty result is a drop and nothing was posted. A file X refuses stops it before anything is posted. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter like
Like one post on X, on the direct-API engine; a dry run unless --live MCP tools: twitterLike, twitterLikeLive, one per form, with named fields.
log twitter like --account <accountName> --tweet <postUrl> [--approvedBy <who>] [--force] [--live] [--json]
Like one post on X, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
X's favorite answer decides: Done (or already liked, code 139) is sent. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter list
The X account's own lists: show them, make one, put someone on one; changes are dry runs unless --live MCP tools: twitterListShow, twitterListCreate, twitterListCreateLive, twitterListAdd, twitterListAddLive, one per form, with named fields.
log twitter list show --account <accountName> [--json]
log twitter list create --account <accountName> --name <name> [--description <text>] [--private] [--approvedBy <who>] [--live] [--json]
log twitter list add --account <accountName> --list <name|id> --handle <handle>|--userId <id> [--approvedBy <who>] [--live] [--json]
X's own lists, on the direct-API engine (vendor/twikit: ListsManagementPageTimeline, CreateList, ListAddMember), through the account's one session (rule 105). show lists the account's lists with their ids and member counts, sending nothing. create makes a list, and a list of that name already there is answered as it is, never made twice. add puts one person on a list, found by its name or id. create and add are dry runs unless --live, which needs --approvedBy (rule 8). Each is a write the governor counts, X's answer decides (rule 73), and a refusal stops with nothing retried (rule 94).log twitter repost
Repost one post on X, on the direct-API engine; a dry run unless --live MCP tools: twitterRepost, twitterRepostLive, one per form, with named fields.
log twitter repost --account <accountName> --tweet <postUrl> [--approvedBy <who>] [--force] [--live] [--json]
Repost one post on X, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
X's retweet answer decides: a created retweet (or already reposted, code 327) is sent. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter follow
Follow one person on X, on the direct-API engine; a dry run unless --live MCP tools: twitterFollow, twitterFollowLive, one per form, with named fields.
log twitter follow --account <accountName> --userId <id> [--approvedBy <who>] [--force] [--live] [--json]
Follow one person on X, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
X's follow answer decides: the followed user back is sent, already following is sent too. Follow before a cold dm: nobody gets a cold message from an account that doesn't follow them. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter dm
Send one approved direct message on X, on the direct-API engine; a dry run unless --live MCP tools: twitterDm, twitterDmLive, one per form, with named fields.
log twitter dm --account <accountName> --userId <id> --text "<copy>" [--media <file|url>] [--approvedBy <who>] [--skipStyleCheck] [--asOne] [--overGate] [--force] [--live] [--json]
Send one approved direct message on X, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80).
Without --live it is a dry run: the target, the text and its house style are checked and X is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). The session is checked first, and the account's governor counts it as a write (240 a day). --force goes past the governor's pause and caps for this one call, only when matt asked (rule 86): X still answers, and its answer still decides.
A draft written on several lines goes out one message per line, and a one-line draft is split the way matt writes in that chat (log voice index), with his usual gap between them; --asOne sends it as one, only when matt asked (rule 90). X's answer decides: a conversation id is sent, none is a drop, and a drop stops the rest: what was sent stays sent, nothing is retried (rule 94). An old DM thread (<a>-<b>) and its X Chat thread (<a>:<b>) with one person are one conversation: X shows them in the same place, and a dm sent through the old DM API shows in their X Chat too. A sent answer is delivered whichever conversation id it names: never resend it and never call it the wrong inbox. A refusal before X is called (styleFailed, a dry run) sent nothing, so that dm is sent once its copy is fixed. Every answer is one event: sent, dropped, throttled, dailyLimit (344), gone (385/433), automated (226), duplicate (187), sessionDead (401), restricted. The session state and X's refreshed cookies go back to Twenty.log twitter react
React to a X message with an emoji, on the direct-API engine; a dry run unless --live MCP tools: twitterReact, twitterReactLive, one per form, with named fields.
log twitter react --account <accountName> --chat <chat> --message <id> --emoji <emoji> [--remove] [--approvedBy <who>] [--live] [--json]
Reacts to one message with an emoji, on the direct-API engine (vendor/twikit, no browser), or takes the account's own reaction off with --remove. X takes any emoji; a name (joy, fire, eyes) is turned into it. It goes through X's own DM reaction mutation (twikit's add_reaction_to_message), so a message only X Chat holds may be refused, and X's answer says so.
Without --live it is a dry run and nothing is contacted. --live reacts, only when matt asked, and needs --approvedBy (rule 8). The chat is claimed first, as for a send, and released once it is done (rule 133). The platform's own answer decides (rule 73), the governor counts it as a write, and a refusal stops with nothing retried (rule 94).log twitter queue
Build, approve, plan and run a queue of approved X replies, quotes or dms at random moments, on the direct-API engine MCP tools: twitterQueueBuild, twitterQueueShow, twitterQueueCheck, twitterQueueStatus, twitterQueueApprove, twitterQueuePlan, twitterQueueRun, twitterQueueRunLive, twitterQueueAdd, twitterQueueNudge, twitterQueueEdit, one per form, with named fields.
log twitter queue build --targets <targets.json> [--variations <a.json,b.json>] --out <queue.json> --account <accountName> [--action reply|quote|dm|voice] [--seed <n>]
log twitter queue show <queue.json> [--json]
log twitter queue check <queue.json> [--skipXChatCheck] [--json]
log twitter queue status <queue.json> [--json]
log twitter queue approve <queue.json> --by <who>
log twitter queue plan <queue.json> --window <5h|90m> [--limit <n>] [--start <time>] [--minGap <s>] [--like] [--likeChance <0-1>]
log twitter queue run <queue.json> [--alertChannel <uuid>] [--alertReplyTo <eventId>] [--alertMention <hex,hex>] [--force] [--keepOpen] [--live]
log twitter queue add <queue.json> --targets <targets.json> --by <who> [--action reply|quote|dm|voice] [--window <2h|90m>] [--minGap <s>]
log twitter queue nudge <queue.json> [--replies <n>]
log twitter queue edit <queue.json> --n <n> --text "<new copy>" --by <who> [--window <30m>]
A queue of approved X replies, quotes or dms, sent one at a time at random moments inside a window, on the direct-API engine (no browser, no proxy: X accounts call X directly, rule 80). Targets come from treg research, never from X (rule 53).
build pairs each target with one unique line from the variations, shuffled; a target that carries its own text (written for that person's post) keeps it. show and status read it; check runs every pending item through the house style and target checks and contacts nothing. approve records who approved it as it stands: a fingerprint of the account and every item's action, target and copy, and any edit afterwards voids it. plan draws every like and reply a time with the system random source, at least --minGap apart (with --like, each post may get a like before or after its reply). nudge tells a running run to send the next replies now. One account has one queue: add puts more in (dms, replies, quotes together), even while it runs, each fitted between what is planned at least minGap apart, and the run takes them in as it waits; run --keepOpen stays running for more, so all of an account's sending is one run through its one X session (rule 105). Right before each send the run reads that person's full context again (their dm thread and X Chat, and what they did on the account's posts from its notifications) and holds the item when anything happened since it was drafted: a message either way, a like, retweet, reply, quote, mention or follow (rule 109). edit puts new copy on a pending or held item (no queue file is ever edited by hand), plans a held one again inside --window, and approves the queue as it then stands.
run walks the plan as an XState machine (planned, waiting, liking, replying, retrying, finished, halted), every transition on the live log: without --live a dry run lists what it would send and when; --live sends, only when matt asked (rule 8). Each answer is X's own event: any refusal (a 429, 226 looks automated, 344 daily limit, a soft block, the governor, a dead session) stops the run at once with X's answer and the time, the item stays pending, and nothing goes out on that account until matt starts a run again (rule 94); with --alertChannel it tags matt in buzz. A deleted post or replies off (385/433) is skipped. A dropped connection with no answer from X is tried again after 1 to 3 min, at most 3 drops in a row. It saves after every step, so running it again resumes. --force sends every event past the account's governor (its pause and caps), only when matt asked (rule 86); X's answers still stop the run.log new
Make a new platform (session, command, MCP tool, engine, docs), wired into the repo through its syntax tree MCP tools: newPlatform, one per form, with named fields.
log new platform <name> [--label <shown name>] [--twentyPlatform <ENUM>] [--library <vendor/folder>] [--sessionCookies <a,b>] [--cookieDomain <.site.com>] [--hosts <a.com,b.com>] [--home <https://…>] [--noProxy] [--noBrowser] [--apply] [--json]
Makes a new platform for log-cli: its account session on the shared platformKit, `log <name> session check` with its MCP tool, a direct-API engine in python/<name>Api on the shared Python kit, and docs/platforms/<name>. The repo is wired by editing each file through its syntax tree (PLATFORMS, PLATFORM_LABELS, COMMANDS, COMMAND_INDEX, createContext, CommandContext, DIRECT_ROUTES, COMMAND_RULES), and every edit is checked to have landed before anything is written.
Without --apply it lists what it would write and change, and writes nothing. Nothing is overwritten: an existing platform or file is refused. It says what Twenty is missing (the socialAccounts.platform option) but never changes Twenty. Then fill in the engine's actions and add the platform's families as commands (rule 83).log reddit session
Check that Reddit still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty MCP tools: redditSessionCheck, one per form, with named fields.
log reddit session check --account <accountName> [--json]
Checks that Reddit still accepts the account's saved session, on the direct-API engine (no browser). Sends nothing.
Reddit's own answer decides. The result goes to socialAccounts.sessionState, and refreshed cookies are merged back into the row.log discord session
Check that Discord still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty MCP tools: discordSessionCheck, one per form, with named fields.
log discord session check --account <accountName> [--json]
Checks that Discord still accepts the account's saved session, on the direct-API engine (no browser). Sends nothing.
Discord's own answer decides. The result goes to socialAccounts.sessionState, and refreshed cookies are merged back into the row.log discord chats
List the account's DMs and group DMs with their ids, on the direct-API engine, sending nothing MCP tools: discordChats, one per form, with named fields.
log discord chats --account <accountName> [--json]
Lists the account's DMs and group DMs with their ids, names and members, on the direct-API engine (no browser). Sends nothing.log discord read
Read a Discord chat or channel's latest messages, on the direct-API engine, sending nothing MCP tools: discordRead, one per form, with named fields.
log discord read --account <accountName> --channel <channelId|chat name> [--limit <n>] [--after <messageId>] [--json]
Reads a DM, group DM or server channel's latest messages, oldest first, on the direct-API engine (no browser): who sent each, when, whether a bot, what it replies to, its text and embeds. --after keeps only what came after that message (a bot's answer to ours). Sends nothing.log discord watch
Watch the account's Discord messages as Discord pushes them over its gateway, nothing polled, sending nothing MCP tools: discordWatch, one per form, with named fields.
log discord watch --account <accountName> [--channel <channelId|chat name>] [--seconds <s>] [--inbox] [--json]
Opens the account's Discord gateway on the direct-API engine (wss://gateway.discord.gg, no browser) and prints every message the moment Discord pushes it, nothing polled. --channel keeps only one chat or channel; --seconds stops after that long (at least 5), otherwise it runs until stopped. --inbox hands each new DM, group message, mention of the account or reply to it from someone else to the inbox hook, which puts it in front of the agent with what to do next. Sends nothing.log discord send
Send one message to a Discord DM, group or channel, on the direct-API engine; a dry run unless --live MCP tools: discordSend, discordSendLive, one per form, with named fields.
log discord send --account <accountName> --channel <channelId|chat name> --text "<message>" [--asOne] [--overGate] [--approvedBy <who>] [--live] [--json]
Sends one message to a DM, a group DM (by its id or its name, from log discord chats) or a server channel (by its id), on the direct-API engine (no browser).
--brief says the message hands someone a task, not a casual reply: the gate reads it as a brief instead of matt's chat voice (rule 162), asking SubQ whether it says clearly what to do, why, what done looks like and by when, kindly, and how to ask when stuck, with the attached files read beside it. The sameness, AI artifact and scrambler checks still run.
--file attaches files (a guide, a spreadsheet) to the last message, uploaded through the account's own session: only files under OUTBOX or ~/.logCli, at most 10 MB each, refused before anything is sent otherwise (fileRefused).
A draft written on several lines goes out one message per line, wherever it is sent (matt, 2026-10-28: stop sending as one). A one-line draft is split the way matt writes in that chat, from his own messages there (log voice index): how many he sends in a row and how many words each runs to, cut at the draft's own line breaks or punctuation, else the nearest space, and sent with his usual gap between them; a chat where he mostly sends one gets one. --asOne always sends one. Without --live it is a dry run and Discord is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). Discord's answer decides: a message id is sent, none is a drop. The account's governor counts it as a write. Before anything is sent, dry run or --live, the copy is read for what AI chat interfaces leave in text (rule 111): hidden characters, odd spaces or pasted Claude HTML refuse it with aiArtifacts, and the reading is printed and kept in --json. --overGate sends it over the voice gate's refusal when matt said to send that exact message: the gate's SubQ read is skipped and the AI artifact check still runs (rule 152).log discord delete
Delete the account's own Discord messages by id, on the direct-API engine; a dry run unless --live MCP tools: discordDelete, discordDeleteLive, one per form, with named fields.
log discord delete --account <accountName> --channel <channelId|chat name> --message <messageId>[,<messageId>...] [--approvedBy <who>] [--live] [--json]
Deletes the account's own messages by id (from log discord read) in a DM, group DM or server channel, on the direct-API engine (no browser): a duplicate or a mistake. Each message is read first and only deleted when the account sent it; someone else's is refused with notOwnMessage. Without --live it is a dry run that reads each message and deletes nothing. --live deletes, only when matt asked, and needs --approvedBy (rule 8). Discord's answer decides (204 is deleted). Each delete counts as a write, they go 4 s apart, and the first refusal stops the run with nothing retried (rule 94).log discord react
React to a Discord message with an emoji, on the direct-API engine; a dry run unless --live MCP tools: discordReact, discordReactLive, one per form, with named fields.
log discord react --account <accountName> --channel <channel> --message <id> --emoji <emoji> [--remove] [--approvedBy <who>] [--live] [--json]
Reacts to one message with an emoji, on the direct-API engine (vendor/endcord, no browser), or takes the account's own reaction off with --remove. Discord takes any emoji, a name (joy, fire, eyes) turned into it, or a server's own emoji as name:id.
Without --live it is a dry run and nothing is contacted. --live reacts, only when matt asked, and needs --approvedBy (rule 8). The chat is claimed first, as for a send, and released once it is done (rule 133). The platform's own answer decides (rule 73), the governor counts it as a write, and a refusal stops with nothing retried (rule 94).log discord voice
Send a voice note in matt's cloned voice as Discord's own voice note, from text; a dry run unless --live MCP tools: discordVoice, discordVoiceLive, one per form, with named fields.
log discord voice --account <accountName> --channel <channel> --text "<what matt says>" [--provider inworld|fishAudio] [--mattsWords] [--translationOf "<what it translates>" --language <code>] [--approvedBy <who>] [--live] [--json]
Sends a voice note in matt's cloned voice (the voice provider, log clone voice) as discord's own voice note: a voice message (flags 8192, its waveform and duration), the bubble Discord's own app records. It takes text only, never an audio file: the words are said in his voice, converted with ffmpeg to ogg/opus and uploaded on the direct-API engine through the account's one session (vendor/endcord, no browser).
A voice note goes out only when matt asks for one (rule 142). The provider is --provider, else LOG_VOICE_PROVIDER, else inworld; the command only ever calls the shared voice layer (rule 143). Without --live it is a dry run: nothing is generated and nothing is contacted. --live needs --approvedBy (rule 8). It says and converts the note while the account catches up (rule 135) and the text goes through the voice gate every send runs (rule 137), so a note that passes goes out without waiting for it to be made; one the gate refuses is never sent. It then claims the chat (rule 133) and releases it after. The governor counts it as a write, the platform's answer decides (rule 73), and a refusal stops with nothing retried (rule 94).
--mattsWords says the text is matt's own words, dictated by him for this voice note: the voice gate's read is skipped, since it grades speech as typed text and as a reply, while the AI artifact check still runs and refuses aiArtifacts (rule 144).
--translationOf "<what it translates>" says the text is a translation, only when matt asked for one: the voice gate grades those original words (his own language, which the gate reads) instead of the translation, which it can't match to his writing; both are read for AI artifacts. --language <code> (zh-CN, es-ES ...) is the language the clone speaks it in, instead of the clone's own, and it is needed with --translationOf (rule 148).log linkedin session
Check that LinkedIn still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty MCP tools: linkedinSessionCheck, one per form, with named fields.
log linkedin session check --account <accountName> [--json]
Checks that LinkedIn still accepts the account's saved session, on the direct-API engine (openLinkedinApi, no browser). Sends nothing.
LinkedIn's own answer decides. The result goes to socialAccounts.sessionState, and refreshed cookies are merged back into the row.log linkedin cookies
Add or refresh a LinkedIn account in Twenty: its session cookies from a masked prompt, checked against LinkedIn Terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal.
log linkedin cookies save --account <publicId> [--name <label>]
Saves a LinkedIn account's session to Twenty from a masked Clack prompt: its li_at and JSESSIONID cookies, copied from a browser signed in to linkedin.com, never passed as flags or printed. --account is the profile's public id, the part after linkedin.com/in/.
They are checked first: the direct-API engine (openLinkedinApi, no browser, no proxy) reads /voyager/api/me with them, and only cookies LinkedIn accepts for that same profile are saved. A profile Twenty doesn't have yet gets a new LINKEDIN socialAccounts row (rule 13); one it has gets its cookies replaced.log slack session
Check that Slack still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to its row MCP tools: slackSessionCheck, one per form, with named fields.
log slack session check --account <accountName> [--workspace <domain>] [--json]
Checks that Slack still accepts the account's saved d cookie, on the direct-API engine (vendor/gabrielSlack, no browser): the workspaces it is signed in to, and with --workspace who it is there (auth.test). Sends nothing.
Slack's own answer decides. The result goes to socialAccounts.sessionState, and refreshed cookies are merged back into the row.log slack workspaces
List the workspaces a Slack account is signed in to, on the direct-API engine, sending nothing MCP tools: slackWorkspaces, one per form, with named fields.
log slack workspaces --account <accountName> [--json]
Lists the workspaces the account's d cookie is signed in to, on the direct-API engine (Slack's own cookie exchange, vendor/gabrielSlack, no browser): each one's id, name and domain, the domain being what --workspace takes. Sends nothing.log slack chats
List a Slack account's DMs and group DMs in a workspace with their ids, on the direct-API engine, sending nothing MCP tools: slackChats, one per form, with named fields.
log slack chats --account <accountName> [--workspace <domain>] [--limit <n>] [--json]
Lists the account's DMs and group DMs in one workspace with their ids and who they are with, newest first, on the direct-API engine (vendor/gabrielSlack, conversations.list, no browser). Sends nothing.log slack read
Read a Slack chat's latest messages, on the direct-API engine, sending nothing MCP tools: slackRead, one per form, with named fields.
log slack read --account <accountName> --chat <chatId|who it is with> [--workspace <domain>] [--limit <n>] [--json]
Reads a DM, group DM or channel's latest messages, oldest first, on the direct-API engine (vendor/gabrielSlack, conversations.history, no browser): who sent each, when, its text and its thread. Sends nothing, and marks nothing read.log slack watch
Watch a Slack workspace's events as its websocket pushes them, nothing polled, sending nothing MCP tools: slackWatch, one per form, with named fields.
log slack watch --account <accountName> [--workspace <domain>] [--chat <chatId|who it is with>] [--seconds <s>] [--inbox] [--json]
Opens each workspace's own websocket on the direct-API engine (vendor/gabrielSlack, wss-primary.slack.com, no browser), every workspace the session is signed in to unless --workspace names one, and prints each message, typing, read marker, reaction and draft the moment Slack pushes it, nothing polled. --chat keeps one chat; --seconds stops after that long (at least 5), otherwise it runs until stopped. --inbox hands each new DM, group message or mention of the account from someone else to the inbox hook, which puts it in front of the agent with what to do next (rule 123). Sends nothing.log slack send
Send a message to a Slack DM, group or channel, on the direct-API engine; a dry run unless --live MCP tools: slackSend, slackSendLive, one per form, with named fields.
log slack send --account <accountName> --chat <chatId|who it is with> --text "<message>" [--workspace <domain>] [--asOne] [--approvedBy <who>] [--live] [--json]
Sends a message to a Slack DM, group DM or channel (by its id, or who it is with from log slack chats), on the direct-API engine (vendor/gabrielSlack, chat.postMessage, no browser).
A draft written on several lines goes out one message per line (rule 90); a one-line draft is split the way matt writes in that chat (log voice index), and --asOne always sends one. Without --live it is a dry run and Slack is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). Slack's answer decides: a ts is sent, anything else stops the run with nothing retried (rule 94). The governor counts each message as a write. The copy is read for what AI chat interfaces leave in text first (rule 111): hidden characters, odd spaces or pasted Claude HTML refuse it with aiArtifacts.log slack cookies
Add or refresh a Slack account locally: its d cookie from a masked prompt, checked against Slack Terminal only: asks for an account's session cookies in a Clack prompt (masked) and checks them with the platform before saving: an X account's auth_token, ct0 and twid, a LinkedIn account's, or a Slack account's d. Run log twitter cookies save --account <handle>, log linkedin cookies save --account <publicId> or log slack cookies save --account <name> in a terminal.
log slack cookies save --account <accountName> [--name <label>] [--workspace <domain>]
Saves a Slack account's session locally (~/.logCli/localAccounts.json, rule 97) from a masked Clack prompt: its d cookie (xoxd-…), copied from a browser signed in to app.slack.com, never passed as a flag or printed.
It is checked first: the direct-API engine (vendor/gabrielSlack, no browser, no proxy) exchanges it with Slack for the workspaces it is signed in to, and only a cookie Slack accepts is saved. An account log doesn't have yet gets a new local SLACK row; one it has gets its d cookie replaced and keeps the rest.log slack react
React to a Slack message with an emoji, on the direct-API engine; a dry run unless --live MCP tools: slackReact, slackReactLive, one per form, with named fields.
log slack react --account <accountName> --chat <chat> --message <id> --emoji <emoji> [--workspace <workspace>] [--remove] [--approvedBy <who>] [--live] [--json]
Reacts to one message with an emoji, on the direct-API engine (vendor/gabrielSlack, reactions.add and reactions.remove, no browser), or takes the account's own reaction off with --remove. Slack reacts by name (joy, fire, eyes, or a workspace's own); the common emoji are turned into their names, and one it doesn't know is refused with reactInvalid, so give its name. Already reacted counts as done.
Without --live it is a dry run and nothing is contacted. --live reacts, only when matt asked, and needs --approvedBy (rule 8). The chat is claimed first, as for a send, and released once it is done (rule 133). The platform's own answer decides (rule 73), the governor counts it as a write, and a refusal stops with nothing retried (rule 94).log slack voice
Send a voice note in matt's cloned voice as Slack's own voice note, from text; a dry run unless --live MCP tools: slackVoice, slackVoiceLive, one per form, with named fields.
log slack voice --account <accountName> --chat <chat> --text "<what matt says>" [--workspace <workspace>] [--provider inworld|fishAudio] [--mattsWords] [--translationOf "<what it translates>" --language <code>] [--approvedBy <who>] [--live] [--json]
Sends a voice note in matt's cloned voice (the voice provider, log clone voice) as slack's own voice note: an audio file shared into the chat through Slack's own upload (files.getUploadURLExternal, the bytes, files.completeUploadExternal), which Slack plays inline. It takes text only, never an audio file: the words are said in his voice, converted with ffmpeg to AAC in .m4a and uploaded on the direct-API engine through the account's one session (vendor/gabrielSlack on curl_cffi, no browser).
A voice note goes out only when matt asks for one (rule 142). The provider is --provider, else LOG_VOICE_PROVIDER, else inworld; the command only ever calls the shared voice layer (rule 143). Without --live it is a dry run: nothing is generated and nothing is contacted. --live needs --approvedBy (rule 8). It says and converts the note while the account catches up (rule 135) and the text goes through the voice gate every send runs (rule 137), so a note that passes goes out without waiting for it to be made; one the gate refuses is never sent. It then claims the chat (rule 133) and releases it after. The governor counts it as a write, the platform's answer decides (rule 73), and a refusal stops with nothing retried (rule 94).
--mattsWords says the text is matt's own words, dictated by him for this voice note: the voice gate's read is skipped, since it grades speech as typed text and as a reply, while the AI artifact check still runs and refuses aiArtifacts (rule 144).
--translationOf "<what it translates>" says the text is a translation, only when matt asked for one: the voice gate grades those original words (his own language, which the gate reads) instead of the translation, which it can't match to his writing; both are read for AI artifacts. --language <code> (zh-CN, es-ES ...) is the language the clone speaks it in, instead of the clone's own, and it is needed with --translationOf (rule 148).log instagram session
Check that Instagram still accepts an account's saved session, on the direct-API engine, sending nothing; the answer goes to Twenty MCP tools: instagramSessionCheck, one per form, with named fields.
log instagram session check --account <accountName> [--json]
Checks that Instagram still accepts the account's saved session, on the direct-API engine (instagrapi, no browser). Sends nothing.
Instagram's own answer decides. The result goes to socialAccounts.sessionState, and refreshed cookies are merged back into the row.log instagram dms
Read an Instagram account's DMs: its threads, or one thread's messages with --with, on the direct-API engine, sending nothing MCP tools: instagramDms, one per form, with named fields.
log instagram dms --account <accountName> [--with <handle|thread name|thread id>] [--limit <n>] [--json]
Without --with, lists the account's Instagram DM threads, newest first: who each is with, its last message and whether it is unread. With --with, reads that one thread's latest messages, oldest first.
On the direct-API engine (vendor/instagrapi, direct_threads and direct_thread, no browser). Sends nothing, and marks nothing seen.log instagram send
Send text and a photo to an Instagram DM, on the direct-API engine; a dry run unless --live MCP tools: instagramSend, instagramSendLive, one per form, with named fields.
log instagram send --account <accountName> --with <handle|thread name|thread id> [--text "<message>"] [--photo <file>] [--asOne] [--approvedBy <who>] [--live] [--json]
Sends to an Instagram DM on the direct-API engine (vendor/instagrapi, no browser): --with is matched to one thread by handle, name or id, or to a new DM with that handle. --photo sends a picture first (direct_send_photo), then the text one message per line (rule 90), --asOne as one message.
Without --live it is a dry run and Instagram is not contacted. --live sends it, only when matt asked, and needs --approvedBy (rule 8). Instagram's answer decides: each message comes back with its id, anything else stops the run with nothing retried (rule 94). The copy is read for what AI chat interfaces leave in text first (rule 111).log instagram watch
Watch an Instagram account's DMs as Instagram's realtime connection pushes them, nothing polled, sending nothing MCP tools: instagramWatch, one per form, with named fields.
log instagram watch --account <accountName> [--seconds <s>] [--inbox] [--json]
Opens Instagram's own realtime connection on the direct-API engine (vendor/instagrapi, MQTT to edge-mqtt.facebook.com, subscribed to the DM sync, no browser) and prints each DM the moment Instagram pushes it, nothing polled. --seconds stops after that long (at least 5), otherwise it runs until stopped. --inbox hands each new message from someone else to the inbox hook, which puts it in front of the agent with what to do next (rule 123). Sends nothing.log instagram react
React to a Instagram message with an emoji, on the direct-API engine; a dry run unless --live MCP tools: instagramReact, instagramReactLive, one per form, with named fields.
log instagram react --account <accountName> --thread <thread> --message <id> --emoji <emoji> [--remove] [--approvedBy <who>] [--live] [--json]
Reacts to one message with an emoji, on the direct-API engine (vendor/instagrapi, no browser), or takes the account's own reaction off with --remove. Instagram takes any emoji; a name (joy, fire, heart) is turned into it. It goes through instagrapi's direct_send_reaction (direct_delete_reaction with --remove) in the account's one session (rule 129).
Without --live it is a dry run and nothing is contacted. --live reacts, only when matt asked, and needs --approvedBy (rule 8). The chat is claimed first, as for a send, and released once it is done (rule 133). The platform's own answer decides (rule 73), the governor counts it as a write, and a refusal stops with nothing retried (rule 94).log instagram voice
Send a voice note in matt's cloned voice as Instagram's own voice note, from text; a dry run unless --live MCP tools: instagramVoice, instagramVoiceLive, one per form, with named fields.
log instagram voice --account <accountName> --with <with> --text "<what matt says>" [--provider inworld|fishAudio] [--mattsWords] [--translationOf "<what it translates>" --language <code>] [--approvedBy <who>] [--live] [--json]
Sends a voice note in matt's cloned voice (the voice provider, log clone voice) as instagram's own voice note: a voice clip (instagrapi's direct_send_voice: the audio uploaded to rupload.facebook.com/messenger_audio, then sent as a voice_attachment with its waveform). It takes text only, never an audio file: the words are said in his voice, converted with ffmpeg to AAC in .m4a and uploaded on the direct-API engine through the account's one session (vendor/instagrapi, in the account's one session, no browser).
A voice note goes out only when matt asks for one (rule 142). The provider is --provider, else LOG_VOICE_PROVIDER, else inworld; the command only ever calls the shared voice layer (rule 143). Without --live it is a dry run: nothing is generated and nothing is contacted. --live needs --approvedBy (rule 8). It says and converts the note while the account catches up (rule 135) and the text goes through the voice gate every send runs (rule 137), so a note that passes goes out without waiting for it to be made; one the gate refuses is never sent. It then claims the chat (rule 133) and releases it after. The governor counts it as a write, the platform's answer decides (rule 73), and a refusal stops with nothing retried (rule 94).
--mattsWords says the text is matt's own words, dictated by him for this voice note: the voice gate's read is skipped, since it grades speech as typed text and as a reply, while the AI artifact check still runs and refuses aiArtifacts (rule 144).
--translationOf "<what it translates>" says the text is a translation, only when matt asked for one: the voice gate grades those original words (his own language, which the gate reads) instead of the translation, which it can't match to his writing; both are read for AI artifacts. --language <code> (zh-CN, es-ES ...) is the language the clone speaks it in, instead of the clone's own, and it is needed with --translationOf (rule 148).log twitter voice
Send a voice note in matt's cloned voice as Twitter's own voice note, from text; a dry run unless --live MCP tools: twitterVoice, twitterVoiceLive, one per form, with named fields.
log twitter voice --account <accountName> --with <with> --text "<what matt says>" [--provider inworld|fishAudio] [--mattsWords] [--translationOf "<what it translates>" --language <code>] [--approvedBy <who>] [--live] [--json]
Sends a voice note in matt's cloned voice (the voice provider, log clone voice) as twitter's own voice note: an X Chat voice message: the m4a encrypted with the conversation key (chat-xdk encrypt_stream), uploaded to the conversation's media store (InitializeXChatMediaUploadMutation, the parts POSTed to ton.x.com, FinalizeXChatMediaUploadMutation) and sent as the message's one AUDIO attachment with its duration (SendMessageCreateMutation), as X Chat's own recorder sends it. It takes text only, never an audio file: the words are said in his voice, converted with ffmpeg to AAC in .m4a (audio/mp4, as X Chat records) and uploaded on the direct-API engine through the account's one session (vendor/twikit with vendor/chatXdk, in the account's one X session, X Chat opened with the saved PIN, no browser).
A voice note goes out only when matt asks for one (rule 142). The provider is --provider, else LOG_VOICE_PROVIDER, else inworld; the command only ever calls the shared voice layer (rule 143). Without --live it is a dry run: nothing is generated and nothing is contacted. --live needs --approvedBy (rule 8). It says and converts the note while the account catches up (rule 135) and the text goes through the voice gate every send runs (rule 137), so a note that passes goes out without waiting for it to be made; one the gate refuses is never sent. It then claims the chat (rule 133) and releases it after. The governor counts it as a write, the platform's answer decides (rule 73), and a refusal stops with nothing retried (rule 94).
--mattsWords says the text is matt's own words, dictated by him for this voice note: the voice gate's read is skipped, since it grades speech as typed text and as a reply, while the AI artifact check still runs and refuses aiArtifacts (rule 144).
--translationOf "<what it translates>" says the text is a translation, only when matt asked for one: the voice gate grades those original words (his own language, which the gate reads) instead of the translation, which it can't match to his writing; both are read for AI artifacts. --language <code> (zh-CN, es-ES ...) is the language the clone speaks it in, instead of the clone's own, and it is needed with --translationOf (rule 148).