Manual export for Threads, Instagram, Facebook Pages, YouTube and TikTok
Issues #19-#23, offline slice. Verified 2026-10-08.
These five platforms need Meta app review, Google API compliance audits or a TikTok Content Posting API audit before an app can post for a user. The owner has not completed any of them, so ReachWeaver has no live adapter for any of the five. manual-export is the supported fallback. It turns one variant (and an optional media manifest) into a creator-ready package. The account holder then posts it themselves in the platform’s own app or website.
The package never sends, uploads, schedules or shares anything. Its status is always manual_export_not_published, and its wording never claims that a post exists. The module has no send path (SEND_PATH = None) and imports no network or publisher code.
Command
Section titled “Command”reachweaver manual-export --platform youtube --variant variant.json \ [--media media/manifest.json] [--account-meta account.json] --out exports/youtube-tour [--json]--platform:threads,instagram,facebook,youtubeortiktokfor the sourced packages below, or any othermanual_onlyid in the distribution registry for a generic package.--outmust be a new path. An existing file or directory, even an empty one, is refused. The directory is created with mode 0700, and each file with mode 0600 (O_EXCL,O_NOFOLLOW).- If preflight finds a blocking error, the command prints
BLOCKED, writes nothing and exits 2. - Output files:
manual-export.json(the full package),CHECKLIST.md, and the exact text. For YouTube that istitle.txtanddescription.txt; for the others it iscaption.txt. Text files contain the exact text, with no added newline.
From Python: platform_preflight(platform, variant, account_meta=None) -> (errors, warnings), build_export(platform, variant, media_manifest=None, account_meta=None) -> dict, render_markdown(package) and write_export(package, out_dir).
Generic package for every other manual-only destination
Section titled “Generic package for every other manual-only destination”Private #44, item C2. Offline only. Every other destination that distribution_registry.json marks reachweaver_delivery: manual_only gets a generic package from the same command, for example --platform dev, x, product_hunt, reddit or hacker_news. The five platforms above keep their own sourced packages, byte for byte (tests/test_manual_package.py::test_cli_output_matches_golden_bytes). facebook_pages maps to --platform facebook. A destination that is not manual_only (Bluesky, Mastodon, Telegram, LinkedIn, GitHub, Snapcraft, Gitee) is refused. The code is reachweaver/manual_package.py: package_preflight(platform, variant, account_meta=None), build_package(...), render_markdown, render_notes and write_package(package, out_dir). It adds no delivery mode, no browser automation, no network access and no registry change.
The generic package knows only what the registries already record:
- Label and help page. The label comes from the distribution registry. The official page is the registry’s
scheduling.official_source, with itschecked_ondate, and only when the registry has one. That page may describe scheduling rather than the composer. Otherwise the package says that the registry has no official page. - Limits. No registry field records a text limit for these destinations, so the text limit is
limit unknown — check the platform, except for the Chinese platforms below. Media rows come only from themedia.pyCONSTRAINTStable for the destination (DEV uses thedevtotable). A row without a value, and a destination without a table, also printlimit unknown — check the platform(tests/test_manual_package.py::test_known_media_rows_come_only_from_media_constraints). The capability registry’scompliance_constraintsfor the destination are listed as registry notes with their source and date, for example X’s “Up to 4 photos, 1 GIF or 1 video per post”. ReachWeaver measures the text (characters, UTF-16 units, UTF-8 bytes) but checks it against no limit. - Media. With
--media, the manifest is validated throughmedia.validate_media_manifest, exactly as above. The package lists each asset whosetargetsinclude the destination id (DEV also acceptsdevto): path, alt text, caption files, detected size and the rights record from the manifest (rights_confirmed,license,attribution,provenance). - Checklist. Verify claims; sign in to the intended account; open the platform yourself; paste
post.txt(andtitle.txtif the variant hasplatform_options.title) exactly; the text-limit line; attach each asset with its alt text and rights; confirm rights, consent and disclosure; and keep the native post URL as first-party evidence. ReachWeaver has no command that records a hand-made post (reconcilecovers only queued sends), so the package records nothing. DEV packages add DEV’s rules: disclose AI assistance in the post, and no AI-generated comments (DEV AI guidelines, checked 2026-10-15). Hacker News adds “do not schedule or automate” (registry schedulingprohibited); Reddit adds “post by hand” (moderator_only). - Output.
manual-export.json(kindreachweaver.manual-package.v1, statusmanual_export_not_published),CHECKLIST.md, andpost.txt(plustitle.txt, andtopics.txtfor a Chinese platform with topics). The same new-directory, 0700/0600 and no-overwrite rules apply, and a blocked package writes nothing.
Chinese platforms
Section titled “Chinese platforms”Weibo, Xiaohongshu, Douyin, Bilibili, WeChat Official Accounts, Zhihu, WeChat Channels, Kuaishou, Toutiao, CSDN, Juejin and OSCHINA get a China-aware generic package (reachweaver/china.py). It adds a china block to manual-export.json and a “China” section to CHECKLIST.md:
- Sourced limits. Each fact has a status:
verified(an official help, rule or legal page; only these block),verified_client(the creator page’s own code, warning only),verified_api(a developer API reference, warning only) orunknown. Weibo’s 5,000-character post and 20 MB image limits block; Xiaohongshu’s 20-character title is measured the way its creator page counts it (ASCII is half a character). - Topics.
platform_options.topics(topic words without#) is written totopics.txtin the platform’s syntax (Weibo#话题#, Xiaohongshu#话题, Bilibili comma-separated tags). The post text is never changed. On Weibo a#tagwithout its closing#, or a topic outside 4 to 32 characters, warns. - Declarations.
ai_generated_label(CAC labelling measures Art. 10, in force 2025-09-01),advertising_label(SAMR Internet Advertising Measures Art. 9) and, on WeChat and Bilibili,originality. Each hasselected: null; the CLI printsDECIDE YOURSELFfor each. - Localization. Text without Chinese characters warns, and the checklist asks for a fluent reviewer and the Asia/Shanghai time zone.
Human-only Chinese spaces (Douban, Maimai, Qzone, Baidu Tieba) keep the notes form below. Full guide and sources: CHINA.md. Tests: tests/test_china.py.
Human-only destinations: write it yourself
Section titled “Human-only destinations: write it yourself”Every destination the registry marks human_only (Reddit, Hacker News, Discord, WhatsApp communities, Facebook Groups, Indie Hackers, Lobsters, Stack Exchange and the rest) gets no agent-written post text. This follows the existing rules: Hacker News text is human-written only, ReachWeaver writes no Reddit text, and engagement drafts skip DEV and HN.
- A variant
textorplatform_options.titlefor such a destination is a blocking error (tests/test_manual_package.py::test_agent_text_or_title_is_blocked_for_every_human_only_destination). - The variant may carry
talking_points: at most 10 short notes of at most 200 characters each. That cap is ReachWeaver’s own, not a platform limit; it keeps notes from carrying a ready-made post. - The package has
content.ready_to_paste: false, notextfield and nopost.txt. It writesNOTES.mdinstead: a private notes form that starts with “Write this yourself.” and has blanks for what you want to say, the community rules you checked, links, and the post URL after you post (tests/test_manual_package.py::test_written_package_has_notes_and_no_text_file).
reachweaver manual-export --platform hacker_news --variant notes.json --out exports/hn-notes# notes.json: {"talking_points": ["Offline-first design", "No auto-posting"], "link_url": "https://example.org/docs"}Batch of manual packages
Section titled “Batch of manual packages”Salvaged from draft PR #65, offline only. manual-export-batch prepares 1 to 25 packages in one run:
reachweaver manual-export-batch --file examples/manual-batch.example.json --out exports/regional-batch [--json]{ "schema_version": 1, "items": [ {"platform": "xiaohongshu", "variant": {"platform": "xiaohongshu", "text": "Exact text"}, "account_meta": {"handle": "@my-account"}, "at": "2026-11-02T19:00:00+08:00"}, {"platform": "threads", "variant": {"platform": "threads", "text": "Exact text"}} ]}- Same checks as one package. Each item goes through
build_export(the five sourced platforms) orbuild_package(every othermanual_onlydestination), so its preflight, limits, checklist and files are exactly whatmanual-export --platformproduces.platformtakes the same values asmanual-export --platform. Media manifests are not taken in a batch; a media format only gets the “nothing validated” warning, so runmanual-export --mediafor an item whose media you want checked. - Whole-batch preflight. Every item is checked before anything is written. One blocking error in any item prints
BLOCKED: nothing written, exits 2 and creates no directory. Malformed batches (other top-level keys, 0 or more than 25 items, unknown item fields) and secret-like keys anywhere are refused. - Human-only destinations are refused. Reddit, Hacker News and every other
human_onlydestination cannot be in a batch: prepare their notes form one at a time withmanual-export, and write the post yourself. A repeated platform and text gets a warning. - SHA-256 of the exact text.
--outmust be a new path; it holds one folder per item (01-xiaohongshu,02-threads, …) written by the single-package writer, plusbatch-manifest.json. The manifest lists, for each item, its folder, platform, public account fields and the SHA-256 of every exact-text file (post.txt,title.txt,topics.txt,caption.txt,description.txt). After writing, each file is read back and compared with that hash; a mismatch stops the run. Folders are 0700 and files 0600, as above. - Advisory time. An item’s optional
atmust be ISO 8601 with an explicit offset. It is recorded asplanned_local_timefor your own planning; nothing is scheduled in ReachWeaver or on any platform. - Code:
reachweaver/manual_batch.py(build_batch,write_batch,manifest), tests intests/test_manual_batch.py. It has no send path (SEND_PATH = None), no network import and opens no browser. A batch is not permission for bulk posting.
To see which route a destination has before you add it to a batch, run reachweaver delivery-options --platform ID (reachweaver/delivery_options.py). It reuses the onboarding blocker audit, so its routes (official_api_adapter, manual_export, draft_export, native_manual, human_only, unsupported) match the troubleshooting table.
Variant file
Section titled “Variant file”{ "platform": "youtube", "format": "video", "text": "Description or caption, exactly as it should appear.", "link_url": "https://example.org/docs", "platform_options": {"title": "ReachWeaver in 60 seconds", "tags": ["oss"], "shorts": false}}textis the caption. For YouTube it is the description.format: Threadstext|link|image|video|carousel(defaulttext); Facebooktext|link|image|video(defaulttext); Instagramimage|video|carousel(required); TikTok and YouTubevideo(required).platform_options: YouTubetitle(required),tags,shorts. Facebooktarget_type(page).- Fields from the canonical
PlatformVariant(id,account_id,campaign_id,locale, …) are accepted and ignored. Unknown fields produce a warning. Secret-like keys (token,password,session, …) block the export. - Choices the account holder must make are never taken from the variant.
privacy_level,privacy_status,made_for_kids,contains_synthetic_media,is_aigc,disable_commentand the like are ignored with a warning, and the package leaves them unselected.
Account metadata (--account-meta) is public information only: account_id, handle, display_name, account_type (page, profile, group, business, creator, personal), and tiktok_api_audit (unaudited|audited|unknown, default unknown). Secret-like keys block the export.
Preflight rules
Section titled “Preflight rules”Errors block the export. Warnings need human judgment.
| Platform | Blocking errors | Warnings |
|---|---|---|
| Threads | More than 500 characters, with emoji-like code points counted by their UTF-8 bytes; more than 5 links | More than one hashtag (only one topic tag per post); a tag longer than 50 characters |
| Caption over 2,200 characters; more than 30 hashtags; more than 20 @ tags; text-only format | A URL in the caption (do not rely on it being tappable); 6 to 30 hashtags (the app is moving to at most five per post or reel) | |
| Target not declared, or declared as a profile, personal account, user, timeline or group; text over the conservative 4,500 cap (no official limit was verified) | — | |
| YouTube | Missing title; title over 100 characters; description over 5,000 UTF-8 bytes; < or > in title or description; tags over 500 characters (commas count, and spaced tags count with quotes); non-video format |
Empty description; description links (clickable only for eligible channels) |
| TikTok | Caption over 2,200 UTF-16 units; non-video format | A URL in the caption |
| All | Placeholder/TODO text, control characters (including DEL), bidirectional override/embedding/isolate characters and other invisible format characters (the same Trojan Source rule policy.validate_post applies to live posts; ZWJ, ZWNJ and LRM/RLM stay allowed), a non-public link_url, secret-like keys, platform mismatch |
Text over ReachWeaver’s conservative policy.py cap (the official limit is what applies to a manual post) |
Media requirements are not duplicated here. With --media, the manifest is validated by reachweaver/media.py (validate_media_manifest): real headers, the sourced CONSTRAINTS table, alt text, SRT/WebVTT captions, rights, provenance and locales. Its errors and warnings are copied into the package, prefixed media:. The package then lists each asset that targets the platform (threads, facebook, instagram, tiktok, or youtube/youtube_shorts), with its path, alt text, caption files and detected dimensions and duration.
image,videoandcarouselformats need a matching asset that targets the platform. A carousel needs at least two.media.pyhas constraint tables forinstagram,tiktokandyoutube_shorts. For Threads, Facebook and long-form YouTube it warns that no verified table exists.- Without
--media, a media format only produces a warning that nothing was validated, plus a checklist step to runmedia-check.
Decisions only the account holder makes
Section titled “Decisions only the account holder makes”Each one is listed in human_decisions with selected: null, required: true and its source.
- TikTok:
privacy_level(optionsPUBLIC_TO_EVERYONE,MUTUAL_FOLLOW_FRIENDS,FOLLOWER_OF_CREATOR,SELF_ONLY; only those your account offers apply). Alsointeractions(comments, Duet, Stitch),commercial_contentandai_generated_label. Public is never pre-selected. The note records the Content Posting API audit status from account metadata (defaultunknown). The audit applies only to API clients; ReachWeaver has none for TikTok. - YouTube:
privacy_status(private,unlisted,public),made_for_kids(a legal declaration) andaltered_or_synthetic_content. - Threads, Instagram and Facebook have no extra platform-assigned decisions in this slice. The checklist still covers account, rights, consent and disclosure.
Verified facts (2026-10-08)
Section titled “Verified facts (2026-10-08)”Each fact is stored in manual_export.FACTS with its value, status, source, date and a short quote. unknown means the official page did not establish the fact. conflicting means official pages disagree; ReachWeaver never relies on such a fact.
| Platform | Fact | Status | Official source |
|---|---|---|---|
| Threads | Text posts limited to 500 characters; emoji counted by UTF-8 bytes | verified | Threads posts |
| Threads | At most 5 links; one topic tag per post; tag 1-50 characters | verified | Threads posts |
| Threads | Permissions threads_basic, threads_content_publish; App Review before users without an app role can grant them |
verified | Threads get started |
| Threads | Account-type requirements | unknown | Not stated on the fetched page; re-checked 2026-10-15, no official page limits publishing to a business or creator profile (see THREADS.md), but no receipt shows that every profile can publish |
| Caption: 2,200 characters, 30 hashtags, 20 @ tags | verified | IG User media | |
| In the app: at most five hashtags per post or reel, rolled out gradually. Separate from the API’s 30, and only a warning | reported (Instagram quoted by Social Media Today, 2025-12-18; Instagram’s help page returned HTTP 400 on 2026-10-17) | Social Media Today | |
| API publishing needs an Instagram professional account; Business vs Creator split not stated | verified / unknown | Content publishing | |
Instagram Login: instagram_business_basic, instagram_business_content_publish. Facebook Login: instagram_basic, instagram_content_publish, pages_read_engagement |
verified | Content publishing | |
| Caption links tappable | unknown | No official statement found; the Help Center documents a profile website link | |
Page posts: Page access token; tasks CREATE_CONTENT, MANAGE, MODERATE; pages_manage_posts, pages_read_engagement, pages_manage_engagement, pages_read_user_engagement, publish_video (video) |
verified | Pages API posts | |
| Creating a post on a user (profile) feed is not possible on that endpoint | verified | User feed reference | |
Groups API permissions (incl. publish_to_groups) removed 2024-04-22 |
verified | Graph API v19.0 changelog | |
| Page post text limit | unknown | Not stated; ReachWeaver’s conservative cap applies | |
| YouTube | Title at most 100 characters; description at most 5,000 bytes; no < or >; tags at most 500 characters |
verified | Videos resource |
| YouTube | privacyStatus: private, unlisted, public; the API defaults to public when omitted |
verified | Videos resource |
| YouTube | Every creator must declare whether videos are made for kids (COPPA) | verified | YouTube Help 9528076 |
| YouTube | Upload scopes: youtube.upload, youtube, youtubepartner or youtube.force-ssl |
verified | videos.insert |
| YouTube | Uploads from unverified API projects created after 2020-07-28 locked to private | conflicting | The revision history entry of 2020-07-28 says yes, until an audit; the videos.insert page (last updated 2026-10-08) says such uploads “are not restricted to private viewing mode”. It concerns API uploads only, not Studio uploads |
| TikTok | Caption at most 2,200 UTF-16 units; privacy_level must match the creator’s privacy_level_options |
verified | Direct Post reference |
| TikTok | “All content posted by unaudited clients will be restricted to private viewing mode”; an audit lifts it; video.publish scope needs approval |
verified | Content Posting API get started |
| TikTok | No default privacy; interaction toggles unchecked; commercial-content toggle off; caption editable; no promotional watermarks | verified | Content sharing guidelines |
| TikTok | Draft-upload scope name; caption links tappable | unknown | Not on the fetched pages |
Acceptance status (offline slice)
Section titled “Acceptance status (offline slice)”This slice delivers the manual fallback and the preflight that every issue names. It does not deliver the live adapters, mock API contract tests, receipt reconciliation or analytics, all of which need the owner’s app review or audit first.
| Issue | Met by this slice | Still open |
|---|---|---|
| #19 Threads | Permissions and review requirements documented with date; manual-export fallback; live publishing stays disabled (registry api_gated, adapter unreachable). Met since (mocked): successful post (tests/test_threads.py::test_happy_path_requests_and_receipt), rejected permission (tests/test_threads.py::test_contract_rejected_permission_blocks_before_claim_and_keeps_post_approved), rate limit (tests/test_threads.py::test_preflight_429_records_cooldown_and_keeps_post_approved), expired token (tests/test_threads.py::test_contract_expired_token_blocks_before_claim_and_keeps_post_approved) and preview/receipt reconciliation without duplicates (tests/test_threads.py::test_approved_preview_matches_the_receipt_and_is_published_exactly_once, tests/test_threads.py::test_reconciled_receipt_cannot_be_applied_twice); see THREADS.md |
Meta app with the Threads use case, the owner as Threads Tester, a long-lived token, a supervised first live receipt and the reviewed registry change |
| #20 Instagram | Professional-account prerequisite and API scopes documented; structured fallback for any account; fixtures cover bad media and missing alt text | Unsupported-consumer-account, missing-permission and asynchronous-processing API fixtures; Login-flow choice; account selection and receipts |
| #21 Facebook Pages | Personal profiles and groups rejected at preflight; Page permissions and Groups removal documented with sources and date | Reporting granted scopes from a real connection; mocked API contract tests; receipts and analytics |
| #22 YouTube | Documented manual YouTube Studio fallback (pinned by tests/test_youtube_visibility.py::StudioFallbackDocTests); the package never claims a video is public; privacy and made-for-kids left to the creator. Met since (mocked, synthetic fixtures): chunk upload (tests/test_youtube.py::test_resumable_upload_in_chunks_then_receipt), partial failure (tests/test_youtube.py::test_partial_failures_after_the_first_byte_stop_and_never_resume), audit gating (tests/test_youtube.py::test_bundled_registry_keeps_youtube_gated, tests/test_youtube.py::test_unaudited_project_private_receipt_for_a_public_request_is_not_published), duplicate avoidance (tests/test_youtube.py::DuplicateAvoidanceTests) and a mocked receipt feeding the analytics sampling report (tests/test_youtube_visibility.py::test_an_adapter_receipt_flows_into_the_sampling_report_consistently) |
Google project verification and audit; a supervised private upload receipt from a real channel; channel-authorized YouTube Analytics API retrieval |
| #23 TikTok | Creator-assisted flow with traceable manual_export_not_published status; exact privacy choice left to the creator, public never pre-selected; audit requirements documented without bypasses. Met since (mocked): unaudited client (tests/test_tiktok.py::test_unaudited_creator_offers_only_private_so_other_levels_are_blocked), unapproved scope (tests/test_tiktok_epic.py::test_init_401_names_the_missing_scope_and_nothing_is_uploaded, tests/test_tiktok_epic.py::test_an_api_scope_not_authorized_body_blocks_init_without_an_upload), invalid creator options (tests/test_tiktok.py::test_invalid_creator_options_block_before_the_claim) and 429 (tests/test_tiktok_epic.py::test_init_429_after_the_claim_is_certain_not_sent_with_a_cooldown_and_is_never_resent, tests/test_tiktok_epic.py::test_creator_info_429_records_a_cooldown_and_keeps_the_post_approved) |
App registration, Content Posting API scopes and audit; a supervised live inbox receipt |
Update (2026-10-09): later slices added a mock-tested adapter with mocked API contract tests for each of #19 to #23 (owner-approved 2026-10-09; see THREADS.md, INSTAGRAM.md, FACEBOOK.md, YOUTUBE.md and TIKTOK.md). Every one stays disabled in the capability registry until a supervised live receipt, so manual export is still the publishing path; app review, real receipts and analytics remain open.
Any future adapter needs its own security review and must keep human approval, dry run, the emergency stop, credential isolation and ambiguous-send reconciliation unchanged.
After you post a package by hand
Section titled “After you post a package by hand”manual-export cannot see what you do with the package. Once you have posted it yourself, record it from its link with reachweaver posted LINK --likes N (see below), or with reachweaver mark-shared --platform P --video FILE --text "..." --url LINK to also remember the video file, so the repeat guard warns you if the same video or wording is about to go out again on that platform.
Measure posts you shared by hand
Section titled “Measure posts you shared by hand”The short way is one command with the link to your post and the numbers you see in the app:
reachweaver posted https://bsky.app/profile/you.bsky.social/post/3kabc --likes 12 --replies 3 --reposts 2reachweaver posted https://www.instagram.com/p/ABC123/ --likes 40 --views 900 --at yesterday --text "Behind the scenes"- Network. Taken from the link’s address: bsky.app (Bluesky), instagram.com, linkedin.com, tiktok.com, youtube.com and youtu.be, threads.net and threads.com, x.com and twitter.com, facebook.com, t.me (Telegram), dev.to, reddit.com, pinterest.com, hashnode.com and hashnode.dev, and the
serverof a Mastodon account in your accounts file. For any other address ReachWeaver asks you for--platform(with the list of names); it never guesses and never opens the link. - Account. The account in your accounts file whose handle the link names, else your only account on that network. If you have several and the link does not say which, it asks for
--accountand lists them. With none, the post is recorded as posted by you without an account, and the output says so. - Numbers.
--likes,--replies(or--comments, the same option),--repostsand--viewsare stored as you typed them (sourceentered-by-hand, observed now). A number you leave out stays unknown, never 0. A wrong value stops the command before anything is written. - When.
--at(yesterday,2026-10-19,'2026-10-19 18:30', …). Without it, a terminal asksWhen did you post it? [today] (e.g. yesterday, 2026-10-19)and Enter means today; a script gets now, and the output saysposted today (DATE) — if not, run again with --at yesterday. - Update later. Run
postedwith the same link and new numbers: it adds them to the post you already recorded (“Updated numbers for … on Bluesky”) instead of recording it twice. This also works for a post ReachWeaver sent. - Correct it. Run
postedwith the same link and--at,--textor--accountto correct a post you recorded by hand (“Corrected: posted 2026-10-20 (was 2026-10-21)”); the 7-day reminder then follows the corrected date. The record stays the same never-sendablepublished_manualrow (its status and empty approval stay as they are), the linkedmark-sharedentry gets the same values, and amanual_publish_correctedevent records the old and new time and account (never the text). A post ReachWeaver sent is never changed this way: its time, text and account are the record of what was sent, so only its numbers can be added. A time in the future, or after numbers you already entered for the post, is refused and nothing changes. - Text.
--textis optional. Without it, reports name the post by its network and day (Instagram post of 18 Oct), and the repeat guard has no wording to compare later drafts with; add--text(or usemark-sharedwith--file,--imageor--video) when you want that check.
posted makes the same published_manual record as mark-shared --url below, with the same limits: it can never be approved, scheduled or sent.
The longer way, mark-shared, also records images, videos and links for the repeat guard. Add the link of the post you published (and, if you like, the account and the time you posted it):
reachweaver mark-shared --platform mastodon --account my-masto \ --url https://social.example.org/@me/109876543210 \ --at 2026-10-20T09:00:00+02:00 --file caption.txtWith --url, mark-shared does two things and sends nothing:
- It records the material for the repeat guard, exactly as without
--url(without--accountit counts for every account on that platform). - It creates a measurable record: a post with status
published_manual(“posted by hand”). It prints its id.--atis the time you posted it (default: now; a time more than 10 minutes in the future is refused). This time is whatreport --sinceandinsightsuse.
Then record its results as for any published post:
reachweaver metrics POST_ID --likes N --replies N --reposts N(recorded now, sourceentered-by-hand; add--observed-at yesterdayor--source native-exportwhen that is true), orimport-metrics --csv FILEwith thatpost_id, from the platform’s own analytics.metrics-fetch --onlinereads public counters for a Mastodon link on your configured server and handle, and for a Bluesky link of the formhttps://bsky.app/profile/did:.../post/...(the post must be by your configured handle). For a Bluesky link with a handle instead of adid:, or for any other platform, usemetricsorimport-metrics;metrics-fetchlists the record asposted_by_hand_without_platform_id.
report, insights, journal and the dashboard’s Posted and Results pages include these posts and label them “posted by hand”. journal --no-by-hand leaves them out.
What it cannot do:
- It cannot be approved, scheduled, sent, retried or reconciled. Every send path refuses it or does not see it.
- It is your own statement. ReachWeaver does not check that the post exists. Record the same link twice and the second call is refused.
- It does not change older posts. A record made without
--urlstays a repeat-guard entry only; record it again with--urlto make it measurable.
The first time you record a post this way, ReachWeaver upgrades an older database once (it rebuilds the posts table so it accepts the new status, keeping every row and approval). Back up state/ first if you like; nothing else changes.
