CLI reference: Draft and approve
25 commands. Every command runs on your own computer; nothing is sent to a social network unless a publishing command is run by the owner with its live switches.
reachweaver idea
Section titled “reachweaver idea”Ideas inbox: save a post idea for later with reachweaver idea “text” (the same as idea add); list, drop or promote one to a NEW blocked campaign scaffold; never creates a draft, approval or send
Save a post idea for later: reachweaver idea “text” (short for idea add “text”); several at once: reachweaver idea “first” “second”. Then list, drop or promote it. Nothing is drafted, queued or sent.
reachweaver idea [-h] ACTION ...No options.
reachweaver idea add
Section titled “reachweaver idea add”Add one or more open ideas (each up to 2000 characters): idea add “first” “second” saves both in one step
reachweaver idea add [-h] [--source SOURCE] [--tag TAG] [--json] TEXT [TEXT ...]| Option | Description |
|---|---|
TEXT |
The idea, in a sentence or two; give several (each in quotes) to save them all at once |
--source SOURCE |
A public https URL the idea comes from (not opened or checked; one idea only) |
--tag TAG |
A lowercase tag; repeatable (at most 8; with several ideas, each gets the tags) |
--json |
Machine-readable result |
reachweaver idea list
Section titled “reachweaver idea list”List ideas, newest last
reachweaver idea list [-h] [--status {open,promoted,dropped}] [--tag TAG] [--json]| Option | Description |
|---|---|
--status STATUS |
Only ideas in this state One of: open, promoted, dropped. |
--tag TAG |
Only ideas with this tag |
--json |
Machine-readable result |
reachweaver idea drop
Section titled “reachweaver idea drop”Mark an open idea dropped (kept in the file; only data-purge with closed_ideas_days removes it)
reachweaver idea drop [-h] [--json] id| Option | Description |
|---|---|
id |
The idea id, e.g. idea-1a2b3c4d |
--json |
Machine-readable result |
reachweaver idea promote
Section titled “reachweaver idea promote”Write a NEW campaign scaffold folder from an open idea; it stays BLOCKED until you write and verify it; nothing is staged, approved or sent
reachweaver idea promote [-h] --campaign NAME --out NEW_DIR [--brand BRAND] [--accounts A] [--objective {awareness,contributors,discussion,qualified_visits,signups}] [--json] id| Option | Description |
|---|---|
id |
The idea id |
--campaign NAME |
The new campaign_id (a lowercase slug) |
--out NEW_DIR |
A NEW folder (0700) for campaign.reachweaver.json and brief.md (0600); refuses an existing one |
--brand BRAND |
Optional brand JSON: sets brand_id; with –accounts one placeholder variant per approved account |
--accounts A |
Optional PUBLIC accounts config used with –brand |
--objective OBJECTIVE |
Campaign objective (default awareness) One of: awareness, contributors, discussion, qualified_visits, signups. |
--json |
Machine-readable result |
reachweaver draft
Section titled “reachweaver draft”Queue a platform-native text draft for an account (repeat –account for one draft per account; each still needs its own approval)
reachweaver draft [-h] --account ACCOUNT (--text TEXT | --file FILE) [--campaign CAMPAIGN] [--source-url SOURCE_URL] [--persona ID] [--optout OPTOUT] [--reply-to ITEM] [--online] [--override-flagged LABEL] [--sources CONFIG] [--json]| Option | Description |
|---|---|
--account ACCOUNT |
Account id; repeat to create one separate draft per account, all or none |
--text TEXT |
|
--file FILE |
|
--campaign CAMPAIGN |
|
--source-url SOURCE_URL |
|
--persona ID |
Which of your personas this post is for (for example work or family). Refuses an account that belongs to another persona and names the one to switch to; default: preferences.default_persona in the profile |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention refuses the draft (and, with –reply-to, a listed parent author) |
--reply-to ITEM |
Issue #17 (owner-approved 2026-10-09; disabled until a live receipt): make this a reply to an engagement item id (bluesky:at://… or mastodon:HOST:ID from engagement-fetch/triage) under one of your own published posts; needs –online to verify the parent; sending stays blocked by the capability registry |
--online |
With –reply-to: allow the unauthenticated read-only GETs that verify the parent (without it the planned requests are printed and nothing is created) |
--override-flagged LABEL |
With –reply-to: explicit human override for a parent that triage flags as suspicious, harassment, spam or moderation_labelled (must name that exact label); opt-out signals are never overridable |
--sources CONFIG |
sources-scan config (#9): refuse when the text (a reply’s too) or –source-url links into any visibility:private repository of that config (private repository URLs are never public links) |
--json |
Machine-readable result: the created post id(s); not with –reply-to |
reachweaver check
Section titled “reachweaver check”Validate a draft before approval
reachweaver check [-h] [--json] [--persona ID] [--optout OPTOUT] [--sources CONFIG] post_id| Option | Description |
|---|---|
post_id |
|
--json |
Machine-readable result: ok and the findings |
--persona ID |
Which of your personas this post is for (for example work or family). Refuses an account that belongs to another persona and names the one to switch to; default: preferences.default_persona in the profile |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention is BLOCKED |
--sources CONFIG |
sources-scan config (#9): refuse when any published text (post, alt texts, video title/tags), a hosted image URL or the source URL links into any visibility:private repository of that config (private repository URLs are never public links) |
reachweaver repeat-check
Section titled “reachweaver repeat-check”Read-only: have we already shared this video, image, wording or link on this platform? Compares a stored post, or material you name, with the local history
reachweaver repeat-check [-h] [--post-id POST_ID] [--platform PLATFORM] [--account ACCOUNT] [--text TEXT] [--file FILE] [--image FILE] [--video FILE] [--link URL] [--days N] [--json]| Option | Description |
|---|---|
--post-id POST_ID |
A stored post to check (its text, images, video and link) |
--platform PLATFORM |
With no –post-id: the platform to check against, e.g. youtube |
--account ACCOUNT |
With no –post-id: the account (same-account repeats are the blocking ones) |
--text TEXT |
With no –post-id: the post text |
--file FILE |
With no –post-id: read the post text from this UTF-8 file |
--image FILE |
An image file; repeatable |
--video FILE |
A video file (compared by its exact bytes) |
--link URL |
A link; repeatable |
--days N |
Look-back for similar (not identical) wording, 1..3650 (default 365); identical files and wording are always found |
--json |
Machine-readable result |
reachweaver mark-shared
Section titled “reachweaver mark-shared”Record material you shared by hand (manual export, a platform app) so later drafts are checked against it; with –url also makes it measurable (posted by hand); local, never sends
reachweaver mark-shared [-h] --platform PLATFORM [--account ACCOUNT] [--text TEXT] [--file FILE] [--image FILE] [--video FILE] [--link URL] [--url URL] [--at WHEN] [--note NOTE]| Option | Description |
|---|---|
--platform PLATFORM |
The platform it was shared on, e.g. youtube |
--account ACCOUNT |
The account it was shared from; without it the record counts for every account on that platform |
--text TEXT |
The post text |
--file FILE |
Read the post text from this UTF-8 file |
--image FILE |
An image file; repeatable |
--video FILE |
A video file |
--link URL |
A link; repeatable |
--url URL |
The link of the post on the platform. With it the post also becomes measurable: a ‘published_manual’ record that metrics, import-metrics, report, insights and the dashboard include, labelled posted by hand; it can never be approved, scheduled or sent |
--at WHEN |
When it was shared (default: now): now, today, yesterday, YYYY-MM-DD (midday), ‘YYYY-MM-DD HH:MM’ (your computer’s time zone) or ISO 8601 with a timezone; with –url this is the post’s send time for report –since and insights |
--note NOTE |
A short label for yourself |
reachweaver review
Section titled “reachweaver review”Owner-only guided approval (#11): walk the drafts one by one, oldest first, in your own terminal; each shows its review sheet and check result, then a approves it through the approve command, s skips, c cancels (type CANCEL), q quits. Needs an interactive terminal; never batches; reply drafts are left out
reachweaver review [-h] [--campaign CAMPAIGN] [--account ACCOUNT] [--approver APPROVER] [--persona ID] [--optout OPTOUT] [--sources CONFIG] [--verify-identity] [--online]| Option | Description |
|---|---|
--campaign CAMPAIGN |
Only drafts of this campaign |
--account ACCOUNT |
Only drafts of this account id |
--approver APPROVER |
Passed to each approve (attribution, not authentication); defaults to $USER/$LOGNAME |
--persona ID |
Passed to each check and approve: refuse an account of another persona |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); passed to each check and approve |
--sources CONFIG |
sources-scan config (#9), passed to each check and approve: refuse links into private repositories |
--verify-identity |
Each approve runs its one read-only identity check (needs –online), as approve –verify-identity |
--online |
Required with –verify-identity |
reachweaver approve
Section titled “reachweaver approve”Approve the exact current text; binds an immutable payload hash (#7), plus a Mastodon account’s visibility/language/spoiler_text or a Postiz account’s server and integration
reachweaver approve [-h] [--persona ID] [--approver APPROVER] [--verify-identity] [--online] [--optout OPTOUT] [--allow-repeat] [--ai-label {applied,not_needed}] [--sources CONFIG] post_id| Option | Description |
|---|---|
post_id |
|
--persona ID |
Which of your personas this post is for (for example work or family). Refuses an account that belongs to another persona and names the one to switch to; default: preferences.default_persona in the profile |
--approver APPROVER |
Who approved (attribution, not authentication); defaults to $USER/$LOGNAME |
--verify-identity |
Wave 9 item 2: run the read-only connect-check identity check once and bind a fingerprint of the verified account (no token or response stored); needs –online. A failed check approves nothing. Binds WHICH account, not WHO approved |
--online |
Required with –verify-identity: allow that one read-only identity request |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention refuses approval |
--allow-repeat |
Approve even though this account has already shared (or has another approved copy of) the same video, image or wording on this platform (repeat-check shows what matched). Bound into the approval, so the send-time repeat check lets this one post through; also an audit event |
--ai-label AI_LABEL |
Posts with images or a video only: your answer to the AI-content label question when ai-label-check says required or unknown for the post’s platform (approval is refused without it). applied: you label it yourself (platform composer or text; ReachWeaver sets no label); not_needed: you decided no label is needed. Bound into the approval (approval/media-3) One of: applied, not_needed. |
--sources CONFIG |
sources-scan config (#9): refuse when any published text (post, alt texts, video title/tags), a hosted image URL or the source URL links into any visibility:private repository of that config (private repository URLs are never public links) |
reachweaver unapprove
Section titled “reachweaver unapprove”Withdraw approval: an approved or scheduled post goes back to draft and its payload binding is cleared; local only
reachweaver unapprove [-h] [--json] post_id| Option | Description |
|---|---|
post_id |
|
--json |
Machine-readable result |
reachweaver cancel
Section titled “reachweaver cancel”Cancel a draft, approved or scheduled post so it is never sent; local only
reachweaver cancel [-h] [--json] post_id| Option | Description |
|---|---|
post_id |
|
--json |
Machine-readable result |
reachweaver schedule
Section titled “reachweaver schedule”Give an approved post a due time (explicit timezone offset); a worker you run still has to publish it
reachweaver schedule [-h] --at AT [--json] post_id| Option | Description |
|---|---|
post_id |
|
--at AT |
Explicit ISO 8601 timezone offset, e.g. 2026-10-15T09:00:00+02:00 |
--json |
Machine-readable result |
reachweaver media-attach
Section titled “reachweaver media-attach”Add one image with required alt text to a DRAFT: a local PNG/JPEG/WebP file for Mastodon (–file, offline) or an owner-hosted HTTPS JPEG URL for Instagram (–url with –online: one read-only GET to hash it). Its SHA-256 (and the URL) are bound at approval (#13, #20); never uploads
reachweaver media-attach [-h] (--file FILE | --url URL) (--alt ALT | --alt-file ALT_FILE) [--focus FOCUS] [--online] [--rights-confirmed] [--license ID] [--created-by {owner,third_party,tool}] [--tool TOOL] [--attribution ATTRIBUTION] [--rights-source-url URL] [--alt-translation LANG=TEXT] [--optout OPTOUT] [--sources CONFIG] post_id| Option | Description |
|---|---|
post_id |
|
--file FILE |
Mastodon: local image file (not a symlink); checked from its header |
--url URL |
Instagram: public HTTPS image URL on a host you control (no query string); Meta fetches this URL itself when the post is sent |
--alt ALT |
Alt text describing the image (required; Mastodon description or Instagram alt_text) |
--alt-file ALT_FILE |
Read the alt text from this UTF-8 file |
--focus FOCUS |
Mastodon only: optional focal point x,y, each -1.0..1.0 (0,0 = centre) |
--online |
Required with –url: allow one read-only GET of the image URL |
--rights-confirmed |
#48: you confirm you hold the rights, consent and likeness permission for this image; required with any other rights option |
--license ID |
#48: SPDX id (for example CC-BY-4.0), ‘owner’ or ‘licensed’; stored with the image and bound at approval |
--created-by CREATED_BY |
#48: who made the image (provenance) One of: owner, third_party, tool. |
--tool TOOL |
#48: with –created-by tool: the generation or editing tool |
--attribution ATTRIBUTION |
#48: attribution text (required for third_party and CC-BY images) |
--rights-source-url URL |
#48: public HTTPS page the image came from (provenance) |
--alt-translation LANG=TEXT |
#48: your own alt text in another language (BCP-47 tag), stored and bound but not sent; repeatable |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention in the alt text refuses the attachment |
--sources CONFIG |
sources-scan config (#9): refuse when the alt text or the image URL links into any visibility:private repository of that config (private repository URLs are never public links) |
reachweaver media-detach
Section titled “reachweaver media-detach”Offline: remove every image attachment from a DRAFT
reachweaver media-detach [-h] post_id| Option | Description |
|---|---|
post_id |
reachweaver ai-declare
Section titled “reachweaver ai-declare”Offline: record your AI-content declarations on a DRAFT: who wrote the text (–authorship) and whether each image or video is realistic AI-made media (–realistic-synthetic); bound at approval for a post with media (approval/media-3) and used by the AI-label precondition
reachweaver ai-declare [-h] [--authorship {human,ai_assisted,ai_generated,clear}] [--realistic-synthetic {yes,no,clear}] [--attachment N] [--json] post_id| Option | Description |
|---|---|
post_id |
|
--authorship AUTHORSHIP |
Who wrote the text: human, ai_assisted or ai_generated (clear removes it) One of: human, ai_assisted, ai_generated, clear. |
--realistic-synthetic REALISTIC_SYNTHETIC |
yes: the image/video could pass as a real photo or recording but was AI-made or altered; no: it is not; clear removes the answer. Applies to every attachment unless –attachment One of: yes, no, clear. |
--attachment N |
Only the Nth attachment (1-based, in send order; a video is 1) |
--json |
Machine-readable result: the declarations and the advice |
reachweaver video-attach
Section titled “reachweaver video-attach”Offline: bind one local MP4 to a TikTok DRAFT (issue #23; owner-approved, disabled until a live receipt); approval binds its SHA-256 and the delivery options; a post has images or one video, never both; never uploads
reachweaver video-attach [-h] --file FILE [--mode {creator_inbox,direct_post}] [--privacy-level {PUBLIC_TO_EVERYONE,MUTUAL_FOLLOW_FRIENDS,FOLLOWER_OF_CREATOR,SELF_ONLY}] [--commercial {none,own_brand,branded_content}] [--allow-comment] [--allow-duet] [--allow-stitch] [--aigc] post_id| Option | Description |
|---|---|
post_id |
|
--file FILE |
Local MP4 (not a symlink), at most 256 MiB |
--mode MODE |
creator_inbox (default): a draft the creator finishes in the TikTok app, never public by itself; direct_post: publishes (second registry switch, off) One of: creator_inbox, direct_post. |
--privacy-level PRIVACY_LEVEL |
direct_post only, required, no default; must be offered by the creator’s creator_info One of: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. |
--commercial COMMERCIAL |
direct_post only, required: commercial disclosure One of: none, own_brand, branded_content. |
--allow-comment |
direct_post only: turn comments on (default off) |
--allow-duet |
direct_post only: turn duets on (default off) |
--allow-stitch |
direct_post only: turn stitchs on (default off) |
--aigc |
direct_post only: label the video as AI-generated |
reachweaver video-detach
Section titled “reachweaver video-detach”Offline: remove the video from a TikTok DRAFT; the file is untouched
reachweaver video-detach [-h] post_id| Option | Description |
|---|---|
post_id |
reachweaver youtube-attach
Section titled “reachweaver youtube-attach”#22 (owner-approved; disabled until a live receipt): attach one local MP4 + upload metadata to a YouTube draft; offline, hashes the file; live upload stays registry-blocked
reachweaver youtube-attach [-h] --file FILE --title TITLE [--privacy {private,unlisted,public}] --made-for-kids {yes,no} --category-id CATEGORY_ID [--tag TAG] [--sources CONFIG] post_id| Option | Description |
|---|---|
post_id |
|
--file FILE |
Local MP4 file (symlinks refused); its SHA-256 is bound |
--title TITLE |
Video title, at most 100 characters, no ‘<’ or ‘>’ |
--privacy PRIVACY |
privacyStatus to request (default private; the API default would be public) One of: private, unlisted, public. |
--made-for-kids MADE_FOR_KIDS |
Required human answer for selfDeclaredMadeForKids (YouTube requires every creator to set it) One of: yes, no. |
--category-id CATEGORY_ID |
Numeric YouTube category id (videoCategories.list) |
--tag TAG |
One tag; repeat (500 characters in total) |
--sources CONFIG |
sources-scan config (#9): refuse when the title or a tag links into any visibility:private repository of that config (private repository URLs are never public links) |
reachweaver youtube-detach
Section titled “reachweaver youtube-detach”#22: remove the video from a YouTube draft; offline
reachweaver youtube-detach [-h] post_id| Option | Description |
|---|---|
post_id |
reachweaver campaign-check
Section titled “reachweaver campaign-check”Offline evidence/brand/variant checks; does not assert source truth
reachweaver campaign-check [-h] --file FILE [--brand BRAND] [--json] [--optout OPTOUT] [--history-db PATH] [--history-days N] [--history-window-days W] [--history-max-posts K]| Option | Description |
|---|---|
--file FILE |
|
--brand BRAND |
Brand file; omitting skips account/voice checking |
--json |
Machine-readable diagnostics |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present) |
--history-db PATH |
Also compare variants with this EXISTING local database’s posts of the same account (read-only; never created or modified). Warnings only. The global –db is not used here |
--history-days N |
History look-back for history_near_duplicate, 1..365 (default 30) |
--history-window-days W |
Rolling window for the history_frequency heuristic, 1..365 (default 7) |
--history-max-posts K |
history_frequency warns at K existing posts per window, 1..1000 (default 7; a ReachWeaver heuristic, not a platform limit) |
reachweaver campaign-preview
Section titled “reachweaver campaign-preview”Offline per-channel previews: exact text, platform length, facets, capability, evidence, rule-based confidence; never stages or publishes
reachweaver campaign-preview [-h] --file FILE --brand BRAND [--accounts PREVIEW_ACCOUNTS] [--utm] [--optout OPTOUT] [--history-db PATH] [--history-days N] [--history-window-days W] [--history-max-posts K] [--json | --markdown]| Option | Description |
|---|---|
--file FILE |
Campaign JSON (read only; never modified) |
--brand BRAND |
Brand JSON, including optional per-account rules |
--accounts PREVIEW_ACCOUNTS |
Optional PUBLIC accounts config (e.g. Mastodon max_characters); same as global –accounts |
--utm |
Show each link with utm_source/medium/campaign and utm_content=variant id |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention is an error |
--history-db PATH |
Also compare variants with this EXISTING local database’s posts of the same account (read-only; never created or modified). Warnings only. The global –db is not used here |
--history-days N |
History look-back for history_near_duplicate, 1..365 (default 30) |
--history-window-days W |
Rolling window for the history_frequency heuristic, 1..365 (default 7) |
--history-max-posts K |
history_frequency warns at K existing posts per window, 1..1000 (default 7; a ReachWeaver heuristic, not a platform limit) |
--json |
Machine-readable preview |
--markdown |
Editable Markdown review sheet |
reachweaver campaign-stage
Section titled “reachweaver campaign-stage”Idempotently stage verified text variants as drafts; never publishes
reachweaver campaign-stage [-h] --file FILE --brand BRAND [--optout OPTOUT] [--json]| Option | Description |
|---|---|
--file FILE |
|
--brand BRAND |
Required explicit brand-account allowlist |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present) |
--json |
Accepted for consistency: the result is always JSON |
reachweaver campaign-export
Section titled “reachweaver campaign-export”Export validated draft pack for manual publishing; no network
reachweaver campaign-export [-h] --file FILE --brand BRAND --out OUT [--optout OPTOUT]| Option | Description |
|---|---|
--file FILE |
|
--brand BRAND |
|
--out OUT |
New output folder; refuses existing output |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present) |
reachweaver claims-check
Section titled “reachweaver claims-check”Offline LEXICAL claim grounding of variants vs declared evidence; not truth verification
reachweaver claims-check [-h] --brand BRAND [--duplicate-threshold DUPLICATE_THRESHOLD] [--json] [--optout OPTOUT] campaign| Option | Description |
|---|---|
campaign |
Campaign JSON file |
--brand BRAND |
Brand JSON (banned_phrases, required_disclosure) |
--duplicate-threshold DUPLICATE_THRESHOLD |
Word 3-gram Jaccard at which variants are near-duplicates, (0,1] (default 0.6) |
--json |
Machine-readable grounding report |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention is an error |
reachweaver review-packet
Section titled “reachweaver review-packet”Offline: write a NEW private folder with the exact text, destination, alt text, content digest and check findings of posts for a co-reviewer; never approves
reachweaver review-packet [-h] (--post-id ID | --campaign-staged NAME) --out OUT [--persona ID] [--optout OPTOUT] [--sources CONFIG] [--json]| Option | Description |
|---|---|
--post-id ID |
A stored post to include (repeatable) |
--campaign-staged NAME |
Every draft, approved or scheduled post staged for this campaign id |
--out OUT |
NEW folder (0700; files 0600); refuses an existing path. Under <state>/review-packets/ review-status finds it without –packet |
--persona ID |
As for check: the persona these posts are for |
--optout OPTOUT |
Opt-out list JSON (default $REACHWEAVER_OPTOUT or config/optout.json if present); a mention is a BLOCKED finding |
--sources CONFIG |
sources-scan config (#9): a post linking into a private repository is refused and a private source URL is redacted |
--json |
Machine-readable result (folder, files, digests) |
reachweaver review-comment
Section titled “reachweaver review-comment”Record a co-reviewer’s comment in the packet’s private comments.json, bound to the post’s content digest; never approves or changes a post
reachweaver review-comment [-h] --packet DIR --post-id ID --reviewer NAME --verdict {looks_good,change_requested,question} --note TEXT [--json]| Option | Description |
|---|---|
--packet DIR |
Folder written by review-packet |
--post-id ID |
A post in that packet |
--reviewer NAME |
Who commented (a label, not authentication) |
--verdict VERDICT |
looks_good is an opinion, not approval One of: looks_good, change_requested, question. |
--note TEXT |
The comment (at most 2000 characters) |
--json |
Machine-readable result |
reachweaver review-status
Section titled “reachweaver review-status”Read-only: reviewer comments per post, STALE when the post changed after the comment; never approves
reachweaver review-status [-h] [--packet DIR] [--json]| Option | Description |
|---|---|
--packet DIR |
One packet (default: every packet under <state>/review-packets/) |
--json |
Machine-readable result |
