socialcli 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of socialcli might be problematic. Click here for more details.

Files changed (224) hide show
  1. socialcli-0.3.0.dist-info/METADATA +168 -0
  2. socialcli-0.3.0.dist-info/RECORD +224 -0
  3. socialcli-0.3.0.dist-info/WHEEL +4 -0
  4. socialcli-0.3.0.dist-info/entry_points.txt +3 -0
  5. socialcli-0.3.0.dist-info/licenses/LICENSE +21 -0
  6. socialctl/__init__.py +0 -0
  7. socialctl/__main__.py +6 -0
  8. socialctl/adapters/__init__.py +6 -0
  9. socialctl/adapters/base.py +68 -0
  10. socialctl/adapters/errores.py +56 -0
  11. socialctl/adapters/facebook.py +180 -0
  12. socialctl/adapters/instagram.py +442 -0
  13. socialctl/adapters/tiktok.py +535 -0
  14. socialctl/adapters/youtube.py +331 -0
  15. socialctl/agent_errors.py +176 -0
  16. socialctl/agent_guide.py +23 -0
  17. socialctl/approval_cli.py +27 -0
  18. socialctl/auth.py +139 -0
  19. socialctl/authflow.py +344 -0
  20. socialctl/brands.py +194 -0
  21. socialctl/capabilities.py +57 -0
  22. socialctl/capabilities.yml +5945 -0
  23. socialctl/cli.py +1766 -0
  24. socialctl/connection_service/__init__.py +0 -0
  25. socialctl/connection_service/__main__.py +33 -0
  26. socialctl/connection_service/app.py +496 -0
  27. socialctl/connection_service/assets/PLATFORM-LOGOS-LICENSE.txt +50 -0
  28. socialctl/connection_service/assets/connection.css +75 -0
  29. socialctl/connection_service/assets/facebook.webp +0 -0
  30. socialctl/connection_service/assets/instagram.svg +194 -0
  31. socialctl/connection_service/assets/socialcli-logo.png +0 -0
  32. socialctl/connection_service/assets/tiktok.png +0 -0
  33. socialctl/connection_service/assets/youtube.svg +1 -0
  34. socialctl/connection_service/landing.py +146 -0
  35. socialctl/connection_service/legacy.py +48 -0
  36. socialctl/connection_service/peer.py +13 -0
  37. socialctl/connection_service/providers.py +324 -0
  38. socialctl/connection_service/settings.py +76 -0
  39. socialctl/connection_service/store.py +124 -0
  40. socialctl/connections/__init__.py +0 -0
  41. socialctl/connections/account_cli.py +438 -0
  42. socialctl/connections/cli.py +379 -0
  43. socialctl/connections/client.py +126 -0
  44. socialctl/connections/credential_cli.py +224 -0
  45. socialctl/connections/credential_store.py +49 -0
  46. socialctl/connections/file_store.py +193 -0
  47. socialctl/connections/keychain.py +78 -0
  48. socialctl/connections/meta_user.py +81 -0
  49. socialctl/connections/rotation_cli.py +116 -0
  50. socialctl/connections/saas_client.py +91 -0
  51. socialctl/connections/temporary_media.py +62 -0
  52. socialctl/delivery_options.py +70 -0
  53. socialctl/engagement.py +200 -0
  54. socialctl/engagement_cli.py +50 -0
  55. socialctl/executor.py +135 -0
  56. socialctl/formatter.py +291 -0
  57. socialctl/hosted_media.py +152 -0
  58. socialctl/identity.py +200 -0
  59. socialctl/inbox.py +298 -0
  60. socialctl/inbox_cli.py +46 -0
  61. socialctl/inventory.py +155 -0
  62. socialctl/inventory_cli.py +171 -0
  63. socialctl/inventory_sources.py +101 -0
  64. socialctl/management/__init__.py +1 -0
  65. socialctl/management/batch_cli.py +115 -0
  66. socialctl/management/batches.py +325 -0
  67. socialctl/management/changes.py +485 -0
  68. socialctl/management/cli.py +227 -0
  69. socialctl/management/comments_cli.py +352 -0
  70. socialctl/management/community_batch_cli.py +63 -0
  71. socialctl/management/community_batches.py +294 -0
  72. socialctl/management/community_changes.py +370 -0
  73. socialctl/management/community_cli.py +142 -0
  74. socialctl/management/community_schema.py +119 -0
  75. socialctl/management/facebook_groups.py +1118 -0
  76. socialctl/management/groups_cli.py +274 -0
  77. socialctl/management/inbox_readers.py +87 -0
  78. socialctl/management/meta_changes.py +197 -0
  79. socialctl/management/meta_cli.py +342 -0
  80. socialctl/management/meta_client.py +631 -0
  81. socialctl/management/meta_comment_inbox.py +565 -0
  82. socialctl/management/meta_comments.py +587 -0
  83. socialctl/management/meta_errors.py +59 -0
  84. socialctl/management/meta_pagination.py +52 -0
  85. socialctl/management/meta_schema.py +128 -0
  86. socialctl/management/meta_webhooks.py +147 -0
  87. socialctl/management/metadata_parts.py +144 -0
  88. socialctl/management/owned_assets.py +85 -0
  89. socialctl/management/owned_changes.py +470 -0
  90. socialctl/management/owned_cli.py +128 -0
  91. socialctl/management/owned_reorder.py +62 -0
  92. socialctl/management/owned_schema.py +368 -0
  93. socialctl/management/reply_context.py +24 -0
  94. socialctl/management/reply_evidence.py +80 -0
  95. socialctl/management/resource_changes.py +407 -0
  96. socialctl/management/resource_cli.py +136 -0
  97. socialctl/management/snippet_readback.py +26 -0
  98. socialctl/management/stories.py +713 -0
  99. socialctl/management/stories_cli.py +172 -0
  100. socialctl/management/youtube.py +323 -0
  101. socialctl/management/youtube_community.py +326 -0
  102. socialctl/management/youtube_metadata.py +122 -0
  103. socialctl/management/youtube_owned.py +259 -0
  104. socialctl/management/youtube_resources.py +305 -0
  105. socialctl/media.py +269 -0
  106. socialctl/media_cli.py +183 -0
  107. socialctl/media_ingress.py +211 -0
  108. socialctl/media_preflight.py +114 -0
  109. socialctl/media_registry.py +301 -0
  110. socialctl/media_requirements.py +52 -0
  111. socialctl/metricas/__init__.py +6 -0
  112. socialctl/metricas/almacen.py +133 -0
  113. socialctl/metricas/analytics_changes.py +295 -0
  114. socialctl/metricas/analytics_cli.py +191 -0
  115. socialctl/metricas/analytics_client.py +304 -0
  116. socialctl/metricas/analytics_ingest.py +260 -0
  117. socialctl/metricas/analytics_schema.py +174 -0
  118. socialctl/metricas/base.py +147 -0
  119. socialctl/metricas/facebook.py +418 -0
  120. socialctl/metricas/insights.py +227 -0
  121. socialctl/metricas/instagram.py +133 -0
  122. socialctl/metricas/modelos.py +87 -0
  123. socialctl/metricas/piezas.py +134 -0
  124. socialctl/metricas/reach.py +94 -0
  125. socialctl/metricas/reporting_client.py +112 -0
  126. socialctl/metricas/resumen.py +251 -0
  127. socialctl/metricas/slugs.py +89 -0
  128. socialctl/metricas/tiktok.py +185 -0
  129. socialctl/metricas/youtube.py +403 -0
  130. socialctl/migration_cli.py +60 -0
  131. socialctl/migration_files.py +68 -0
  132. socialctl/models.py +150 -0
  133. socialctl/native_schedule/__init__.py +13 -0
  134. socialctl/native_schedule/api_delivery.py +15 -0
  135. socialctl/native_schedule/api_reconcile.py +40 -0
  136. socialctl/native_schedule/approval.py +160 -0
  137. socialctl/native_schedule/cadence.py +73 -0
  138. socialctl/native_schedule/cli.py +510 -0
  139. socialctl/native_schedule/dispatcher.py +120 -0
  140. socialctl/native_schedule/facebook.py +112 -0
  141. socialctl/native_schedule/facebook_reels.py +140 -0
  142. socialctl/native_schedule/first_comment.py +181 -0
  143. socialctl/native_schedule/handoff.py +193 -0
  144. socialctl/native_schedule/instagram_delivery.py +224 -0
  145. socialctl/native_schedule/instagram_runtime.py +66 -0
  146. socialctl/native_schedule/inventory.py +70 -0
  147. socialctl/native_schedule/migration.py +310 -0
  148. socialctl/native_schedule/models.py +103 -0
  149. socialctl/native_schedule/reconcile.py +83 -0
  150. socialctl/native_schedule/remote_data.py +177 -0
  151. socialctl/native_schedule/report.py +151 -0
  152. socialctl/native_schedule/store.py +560 -0
  153. socialctl/native_schedule/verification.py +32 -0
  154. socialctl/native_schedule/youtube.py +339 -0
  155. socialctl/operation_output.py +87 -0
  156. socialctl/plantillas/accounts.yml +33 -0
  157. socialctl/plantillas/brand.md +95 -0
  158. socialctl/plantillas/estrategia.md +64 -0
  159. socialctl/postfile.py +240 -0
  160. socialctl/preflight.py +73 -0
  161. socialctl/publication_policy.py +34 -0
  162. socialctl/publication_status.py +75 -0
  163. socialctl/publication_steps.py +398 -0
  164. socialctl/publish_output.py +96 -0
  165. socialctl/publisher.py +445 -0
  166. socialctl/queue_migration.py +302 -0
  167. socialctl/read_budget.py +224 -0
  168. socialctl/read_reports.py +91 -0
  169. socialctl/rutas.py +103 -0
  170. socialctl/saas/__init__.py +1 -0
  171. socialctl/saas/access.py +75 -0
  172. socialctl/saas/auth.py +213 -0
  173. socialctl/saas/backup.py +124 -0
  174. socialctl/saas/billing.py +327 -0
  175. socialctl/saas/billing_enrollment.py +1237 -0
  176. socialctl/saas/billing_enrollment_schema.py +519 -0
  177. socialctl/saas/billing_inventory.py +775 -0
  178. socialctl/saas/billing_reconcile.py +127 -0
  179. socialctl/saas/billing_routes.py +27 -0
  180. socialctl/saas/budget.py +66 -0
  181. socialctl/saas/connections.py +374 -0
  182. socialctl/saas/consent.py +52 -0
  183. socialctl/saas/lifecycle.py +341 -0
  184. socialctl/saas/mail.py +89 -0
  185. socialctl/saas/maintenance.py +68 -0
  186. socialctl/saas/meta_user.py +298 -0
  187. socialctl/saas/meta_webhooks.py +358 -0
  188. socialctl/saas/migration.py +107 -0
  189. socialctl/saas/models.py +8 -0
  190. socialctl/saas/operations.py +102 -0
  191. socialctl/saas/pages.py +126 -0
  192. socialctl/saas/pairing.py +158 -0
  193. socialctl/saas/permissions.py +57 -0
  194. socialctl/saas/reminders.py +128 -0
  195. socialctl/saas/renewal.py +74 -0
  196. socialctl/saas/restore_guard.py +229 -0
  197. socialctl/saas/revocation.py +97 -0
  198. socialctl/saas/rotation.py +114 -0
  199. socialctl/saas/routes.py +221 -0
  200. socialctl/saas/settings.py +34 -0
  201. socialctl/saas/store.py +106 -0
  202. socialctl/saas/temporary_media.py +245 -0
  203. socialctl/saas/webhooks.py +76 -0
  204. socialctl/schedule_remote.py +262 -0
  205. socialctl/schedule_sqlite.py +44 -0
  206. socialctl/scheduler.py +581 -0
  207. socialctl/scheduler_launchd.py +319 -0
  208. socialctl/scheduler_systemd.py +167 -0
  209. socialctl/source_links.py +119 -0
  210. socialctl/ui/__init__.py +1 -0
  211. socialctl/ui/assets/agent-manual.md +850 -0
  212. socialctl/ui/assets/agents.md +27 -0
  213. socialctl/ui/assets/components.css +20 -0
  214. socialctl/ui/assets/connection-layout.css +53 -0
  215. socialctl/ui/assets/otp.template.html +10 -0
  216. socialctl/ui/assets/tokens.css +1 -0
  217. socialctl/ui/assets/trial.template.html +11 -0
  218. socialctl/ui/assets/web-layout.css +38 -0
  219. socialctl/ui/email/otp.html +10 -0
  220. socialctl/ui/email/trial.html +11 -0
  221. socialctl/upload_consent.py +13 -0
  222. socialctl/workspace.py +8 -0
  223. socialctl/youtube_data.py +471 -0
  224. socialctl/youtube_data_service.py +225 -0
@@ -0,0 +1,850 @@
1
+ # SocialCli for AI agents — 0.3.0
2
+
3
+ Setup instructions for SocialCli 0.3.0. Require published metadata before installing;
4
+ do not replace an existing 0.2.6 installation or migrate its credentials silently.
5
+ Stripe remains in test mode. Google is External / In production with verified branding, but sensitive YouTube scopes remain unverified, with an unverified-app warning and user cap.
6
+ Real-agent acceptance is performed by the owner; do not claim it from installation.
7
+
8
+ SocialCli gives terminal-enabled agents tools for creator-owned social accounts.
9
+ You need a persistent shell workspace, Python 3.11+, macOS or Linux, and protected
10
+ credential storage. SocialCli does not include a model or MCP server. Provider
11
+ availability depends on account eligibility; Google sensitive-scope verification is still pending.
12
+
13
+ ## Install the reviewed release
14
+
15
+ Use the official PyPI release metadata (or TestPyPI `https://test.pypi.org/pypi/socialcli/0.3.0/json` during pre-PyPI verification):
16
+ https://pypi.org/pypi/socialcli/0.3.0/json
17
+
18
+ 1. Require HTTP 200 and package name `socialcli`, version `0.3.0`. If this
19
+ release is unavailable, stop and report it; do not install a similarly named package.
20
+ 2. Select exactly `socialcli-0.3.0-py3-none-any.whl` from `urls`. Download its
21
+ HTTPS `url` and verify the file against that entry's `digests.sha256`.
22
+ 3. Install that verified local wheel into an isolated environment. Resolve
23
+ dependencies from normal PyPI, not TestPyPI. For example:
24
+
25
+ uv tool install --python 3.12 '/absolute/path/socialcli-0.3.0-py3-none-any.whl[headless-credentials]'
26
+ socialcli --version
27
+ socialcli agent-guide
28
+
29
+ Without uv, use a new Python virtual environment and its `python -m pip install`
30
+ with the same local wheel and `headless-credentials` extra. Do not use system pip.
31
+ If an existing SocialCli tool is installed, use a separate virtual environment
32
+ for this pilot and its absolute executable; do not upgrade it automatically.
33
+
34
+ Verify the installed version is exactly 0.3.0 before continuing. Verify the
35
+ wheel's SHA256 before installing rather than running an unverified `uv tool install socialcli`.
36
+
37
+ For headless agents, configure encrypted-file storage with an external key file with mode `0600` in
38
+ a directory with mode `0700` before login. Keep the key outside the workspace and preserve both
39
+ across conversations. Never fall back to plaintext storage. Follow the protected
40
+ storage procedure at:
41
+ https://socialcli.app/connections.html#headless-encrypted-capability-storage.
42
+
43
+ If uv is missing, use the virtual-environment route or its official installation
44
+ instructions at https://docs.astral.sh/uv/getting-started/installation/.
45
+ If Python is older than 3.11, select a supported interpreter first. If the installed
46
+ tool is not on PATH, use its absolute executable or `uv tool update-shell` and
47
+ start a new shell. Video validation needs ffprobe (from FFmpeg); missing ffprobe
48
+ must be resolved before a video submission. Never generate or reencode media.
49
+
50
+ ## Authorize the installation, then the social account
51
+
52
+ The owner must accept the current privacy policy in the browser and separately acknowledge assistant data access. Check the actual assistant plan/settings: Google data must not be used for advertising or general-purpose model training or unrelated human access/transfer. If these controls cannot be established, do not connect that configuration. An agent compatibility label is not provider-wide approval. Existing installations without the new receipt must be revoked and linked again by the owner; never erase local credentials as a shortcut.
53
+
54
+ Use `https://pilot.socialcli.app` for this pilot. The main site is the human landing.
55
+ Ask for the brand name; never infer it. Replace MyBrand below with that exact name.
56
+ Keep the workspace outside the application repository:
57
+
58
+ socialcli brand new MyBrand
59
+ mkdir -m 700 -p "$HOME/.local/share/socialcli-credentials"
60
+ socialcli credentials generate-key --key-file "$HOME/.local/share/socialcli-credentials/master.key"
61
+ socialcli credentials configure --brand MyBrand --backend encrypted-file --store-file "$HOME/.local/share/socialcli-credentials/store.json" --key-file "$HOME/.local/share/socialcli-credentials/master.key" --yes
62
+ socialcli account login --brand MyBrand --service https://pilot.socialcli.app --no-browser --label "My AI assistant"
63
+
64
+ Give the printed URL directly to the user. Do not open, fetch, preview or follow
65
+ it yourself. Keep the process alive with a runner timeout longer than 30 minutes.
66
+ The user signs in by email and explicitly approves this installation. Do not ask
67
+ for their OTP, password, session cookie or provider token in chat.
68
+
69
+ socialcli connect youtube --brand MyBrand --management --analytics --no-browser
70
+
71
+ Use the same browser handoff for TikTok:
72
+
73
+ socialcli connect tiktok --brand MyBrand --no-browser
74
+
75
+ Facebook and Instagram also use `connect PLATFORM --no-browser`; add the
76
+ documented management/analytics options only when needed and owner-authorized.
77
+ Never ask SaaS users for a developer `client_key`, `client_secret` or localhost
78
+ OAuth setup. After account login, `auth PLATFORM` also routes to the SaaS
79
+ connection flow. Without a signed-in installation it fails with login guidance.
80
+ Legacy developer-owned app prompts require explicit `auth --independent` in an
81
+ interactive terminal. Do not use that mode for SaaS users.
82
+
83
+ The provider step has its own 10-minute window. The user grants access and chooses
84
+ the exact account in their browser; the SaaS flow does not require guessing an
85
+ account ID or interactive terminal confirmation. Connecting does not authorize
86
+ publication. Do not use --yes as blanket authorization.
87
+
88
+ The trial lasts 14 days from the first successful connection, without a card.
89
+ Then hosted access costs USD 10 per month. You can subscribe during the trial without losing free days; the first monthly charge is due at its original end.
90
+ Only the user's browser completes payment. The current candidate uses Stripe
91
+ test mode; it is not collecting live subscriptions.
92
+
93
+ ## Persist and recover
94
+
95
+ Managed YouTube API observations expire after six days. User-supplied originals and independent Studio imports are preserved. Re-read the platform when needed; do not report absent old snapshots as zero or invent weekly growth. Unknown ages and failed cleanup block affected data. Disconnect, terminal authorization failure and account deletion trigger a managed-data purge when observed by this runner. Exported reports, chats and independent backups require separate deletion.
96
+
97
+ Concurrent commands can briefly contend for a managed-store lock. Cleanup waits
98
+ up to two seconds per lock. `local_data_busy` means wait for the other command to
99
+ finish and retry; it does not mean corruption and never calls for deleting files.
100
+ Retention remains enforced while cleanup is blocked.
101
+
102
+ Preview the daily cleanup service for this exact brand and root, show it to the owner, then install only with their authorization:
103
+
104
+ ```sh
105
+ socialcli data-cleanup-install --brand NAME --root /absolute/workspace --dry-run
106
+ socialcli data-cleanup-install --brand NAME --root /absolute/workspace --yes
107
+ socialcli data-cleanup-status --brand NAME --root /absolute/workspace
108
+ socialcli data-cleanup --brand NAME --root /absolute/workspace
109
+ # Only for an authorized deletion request:
110
+ socialcli data-cleanup --brand NAME --root /absolute/workspace --purge-youtube
111
+ ```
112
+
113
+ The daily macOS LaunchAgent needs the user session; the Linux user timer needs its user manager (check Linger in status). No cleanup can execute on a stopped runner. Status must confirm the supervisor; a preview is not installation. Do not install on another brand or infer the workspace. Keep the original interpreter/venv available.
114
+
115
+ Keep the brand folder, its .secrets metadata and the configured credential backend
116
+ between conversations. Never upload credentials or keys to Git. A changed path
117
+ can change OS-keyring lookup; use the same absolute SOCIALCLI_ROOT or --root.
118
+ Ephemeral agents must arrange protected persistent storage before authorizing.
119
+ A fresh conversation alone does not require reconnecting YouTube.
120
+
121
+ socialcli account status --brand MyBrand --json
122
+ socialcli connections --brand MyBrand --json
123
+
124
+ `connections` retains successful platforms and identifies each failed platform in
125
+ `errors` with a fixed code, next action and HTTP status when available. Exit 3 is
126
+ partial, exit 1 means the read failed, and exit 0 is complete. Do not discard a
127
+ healthy YouTube result because another platform returns `connection_unavailable`.
128
+ After the owner revokes and relinks an assistant, explicit `account attach` can
129
+ restore an existing approved connection even when its old capability was deleted.
130
+ The current installation must still have owner-approved access, and an existing
131
+ local account must match exactly. This does not repeat OAuth or approve publication.
132
+
133
+ Use `account attach PLATFORM --brand MyBrand` to attach an existing owner-approved
134
+ connection after signing in on a second installation. Use `account recover
135
+ PLATFORM --brand MyBrand` after interrupted local installation; do not blindly
136
+ repeat OAuth. `account rotate --brand MyBrand` renews this installation's local
137
+ credentials. A network failure is not proof the social account was revoked.
138
+ Check --help on the installed version before using a proposed flag.
139
+
140
+ Preserve stdout JSON separately from stderr progress. Give read operations their
141
+ own --timeout and a longer runner limit. For stats, inbox and report, exit 3 means partial observations; exit 1 means error. Older clients and other read commands may return partial data with exit 1;
142
+ never treat an incomplete empty inbox as proof there are no comments.
143
+
144
+ ## Publication gate
145
+
146
+ For every YouTube upload, show the Community Guidelines certification in the complete preview. The owner’s approval must include that statement; account connection is not upload consent. Old upload approvals without the certification require a fresh preview and approval. Never accept privacy or assistant terms on the owner’s behalf.
147
+
148
+ Use only media supplied by the user. Show the complete --dry-run preview and wait
149
+ for explicit approval of that exact account and content before any publication.
150
+ Keep confirmed, pending and uncertain results distinct. Never repeat an uncertain
151
+ write. Sign-in, subscription and connection approval do not approve publication.
152
+
153
+ ## Temporary Instagram media
154
+
155
+ For SaaS Instagram connections, `publish` accepts local JPEG, MP4 and MOV files
156
+ without `media_url_base`. Approved original bytes are uploaded to the SocialCli
157
+ server on florentino.pro (up to 512 MiB), then fetched by Instagram through an
158
+ opaque HTTPS URL. The URL expires 15 minutes after reservation, including upload
159
+ time. The CLI requests deletion when the operation ends; automatic cleanup also
160
+ handles crashes and interrupted uploads. Media is excluded from backups.
161
+ This is temporary staging, not a file library. Keep the original on your runner.
162
+ A scheduled local job uploads only at dispatch; its runner must remain available.
163
+ Native Instagram video delivery already supports direct upload to Meta. Native
164
+ image/carousel helpers with explicit URLs retain their existing hosting contract.
165
+
166
+ ## Canonical publishing workflow
167
+
168
+ Read this installed guide with `socialcli agent-guide`. It works offline. Keep the
169
+ same explicit workspace for every command (`SOCIALCLI_ROOT` or `--root`). Inspect
170
+ `socialcli delivery-options --brand MyBrand --json` before promising a delivery mode.
171
+ That report describes configuration, not verified provider authorization.
172
+
173
+ For an existing user-supplied post and original media:
174
+
175
+ socialcli publish my-post --brand MyBrand --only youtube --dry-run
176
+
177
+ Show the complete preview, including account, text, media, privacy and follow-up
178
+ comments. Wait for the user's explicit approval of that exact preview. Then:
179
+
180
+ socialcli publish my-post --brand MyBrand --only youtube --yes
181
+
182
+ `--yes` only bypasses a terminal prompt after approval. Inspect the persisted
183
+ result and publication journal before repeating any command. Upload accepted,
184
+ scheduled, public and awaiting creator confirmation are different outcomes.
185
+ Use `comments publication-status PUBLICATION_ID --brand MyBrand` to inspect a
186
+ recorded follow-up; never upload the video again to retry a comment.
187
+
188
+ TikTok defaults to `mode: inbox` in the brand's accounts.yml: the file goes directly
189
+ to TikTok and the creator finishes from its inbox notification. Report **awaiting
190
+ creator confirmation**, not published or scheduled. `mode: direct` is disabled and excluded
191
+ from the current offer, regardless of `auditada`. Use creator-reviewed inbox
192
+ upload; the creator must finish publishing in TikTok. Configuration is not
193
+ provider approval evidence.
194
+
195
+ SaaS Instagram publishing uses temporary hosting automatically when no
196
+ `media_url_base` is configured. Independent credentials still need their own
197
+ HTTPS media host. Video/image preparation
198
+ and validation never authorize generating, cropping or transcoding the originals.
199
+
200
+ ## Scheduling and temporary media
201
+
202
+ YouTube remote scheduling receipts need a verified platform reconciliation at least every six days. Without it, receipt expiry blocks local management while preserving the occupied slot; a video already scheduled on YouTube can still publish there. Use YouTube Studio to inspect or cancel it if the local receipt expired. Do not dispatch a replacement or assume the remote schedule was canceled. Supplied future plans without provider receipts are preserved.
203
+
204
+ The SaaS stores account and connection metadata. Instagram may temporarily stage
205
+ original media for at most 15 minutes after reservation; it is excluded from backups.
206
+ Do not use the server for documents, archival media or long-lived scheduling files.
207
+ Other uploads go from the customer’s runner directly to the destination platform.
208
+
209
+ YouTube and Facebook can hold future publications on the platform. SocialCli's
210
+ native API route is a gated candidate, not a completed public pilot. Before using it,
211
+ the operator must record real account/project eligibility and calendar pilot evidence;
212
+ agents must never fabricate that evidence or select their own account.
213
+
214
+ socialcli native-schedule prepare native.json --brand MyBrand --dry-run
215
+
216
+ Show the complete native preview and its final time; after the creator approves the
217
+ exact digest, persist and approve that same intent:
218
+
219
+ socialcli native-schedule prepare native.json --brand MyBrand --persist --approval-digest APPROVED_DIGEST
220
+ socialcli native-schedule approve JOB_ID --brand MyBrand --approval-digest APPROVED_DIGEST
221
+ socialcli native-schedule submit-api JOB_ID --brand MyBrand --eligibility operator-pilot.json
222
+ socialcli native-schedule status --brand MyBrand
223
+
224
+ For new customer schedules, use `schedule: {"mode":"exact","timezone":"Europe/Madrid"}`
225
+ with the customer's actual IANA timezone and a matching local offset in `publish_at`.
226
+ Each publication can have its own date, time and timezone, including multiple posts
227
+ per day. SocialCli preserves the exact instant or rejects a conflict; it never moves
228
+ an exact request to 09:00 or another day. Explicit offsets resolve repeated DST hours;
229
+ nonexistent local times are rejected. Complete input examples are included below.
230
+ Manifests without `schedule` retain the legacy 09:00 America/New_York daily policy.
231
+ Existing legacy reservations still protect their whole New York date; they must not
232
+ be silently rewritten or bypassed. Facebook API delivery currently covers
233
+ Reels only, with a conservative window greater than ten minutes and at most 28 days.
234
+
235
+ `pending_verification` means the API response was checked but calendar confirmation
236
+ is still pending. `uncertain` means reconcile the saved remote attempt, never start
237
+ another upload. Only verified platform scheduling removes the need for a running
238
+ agent at publication time. First comments remain separate work after public visibility.
239
+ If API eligibility is unavailable, prepare a separately approved official UI handoff;
240
+ never change an approved route or publish immediately as a fallback.
241
+
242
+ Instagram and TikTok's posting APIs do not provide this native future-publication
243
+ flow. Offer publication now (or TikTok creator review) in the SaaS launch scope.
244
+ The compatibility `schedule` queue for Facebook/Instagram is local: it needs the
245
+ original file, credentials, connectivity and a persistent executor at delivery time.
246
+
247
+ socialcli schedule-doctor --brand MyBrand --json
248
+
249
+ Exit 1 requires attention. A recent heartbeat proves only a recent run, not that
250
+ Muse/Grok will keep an executor alive between conversations. On a persistent,
251
+ customer-owned Linux machine, `schedule-install --brand MyBrand` installs systemd
252
+ user service/timer units for that exact workspace and installed interpreter. On
253
+ macOS it loads a launchd agent. Both fail instead of reporting success from files alone.
254
+
255
+ Linux units run `run-due` without a shell, five seconds after timer activation and
256
+ 60 seconds after each completed run. They cannot keep a disappearing agent runtime
257
+ alive. The user manager must be available. The installer does not change host policy:
258
+ if logout persistence is unverified, an authorized operator can enable linger with
259
+ `loginctl enable-linger USER_ID` and then run `schedule-doctor` again.
260
+
261
+ On Linux, doctor exits 0 only when the matching loaded configuration, enabled active
262
+ timer, service result, logout persistence and recent executor heartbeat are verified
263
+ now. Even `ready` does not guarantee future power, credentials, files or connectivity.
264
+ macOS persistent supervision still requires operator verification beyond heartbeat.
265
+ Use `schedule-uninstall --brand MyBrand --root WORKSPACE` to disable future Linux
266
+ runs; an already running approved operation may finish. Queue, media and credentials
267
+ remain intact. Different workspace roots have different Linux unit identities.
268
+ This is not a reason to move media onto the SaaS server.
269
+
270
+ ## Native scheduling input examples (offline)
271
+
272
+ These are fictional schemas, not authorization or pilot evidence. Store each file
273
+ inside the selected brand directory under `SOCIALCLI_ROOT`. Replace every sample
274
+ identity, date, copy and hash with verified values. Keep the user's original
275
+ `clip.mp4` there; never generate a replacement. The example destination must match
276
+ `facebook.page_id` in that brand's accounts.yml. All timestamps require an offset.
277
+
278
+ First read the complete official destination calendar for the publication period.
279
+ Save `native-calendar.json` with the actual observation time (at most 24 hours old),
280
+ coverage spanning full local days, and every existing scheduled item. An empty
281
+ `objects` array is valid only after verifying that the covered calendar is empty;
282
+ never substitute an API error or partial read for a complete inventory. Each object
283
+ has `platform`, `account_id`, `remote_ref`, `publish_at` and optional `occurrence_id`.
284
+
285
+ Example `native-calendar.json`:
286
+
287
+ ```json
288
+ {
289
+ "observed_at": "2035-01-01T12:00:00Z",
290
+ "coverage_start": "2035-01-01T00:00:00-05:00",
291
+ "coverage_end": "2035-01-08T00:00:00-05:00",
292
+ "accounts": [{"platform": "facebook", "account_id": "page-example", "complete": true}],
293
+ "objects": []
294
+ }
295
+ ```
296
+
297
+ Example `native.json` (standalone Reel, no source-video follow-up). `dispatch_after`
298
+ is the earliest upload time, not publication time. It must precede `publish_at`;
299
+ submission also obeys existing account cooldowns. The example uses the customer's
300
+ chosen 19:30 in Europe/Madrid. Confirm that timezone with the customer; never infer
301
+ it from the server, the agent's machine or a brand name. Inspect the local time,
302
+ UTC time and **final** preview before approving. Platform eligibility and publication
303
+ windows still apply; choosing a time does not prove the platform accepted it.
304
+
305
+ ```json
306
+ {
307
+ "id": "example-reel",
308
+ "platform": "facebook",
309
+ "account_id": "page-example",
310
+ "publish_at": "2035-01-03T19:30:00+01:00",
311
+ "dispatch_after": "2035-01-01T12:00:00Z",
312
+ "route": "api",
313
+ "schedule": {"mode": "exact", "timezone": "Europe/Madrid"},
314
+ "payload": {
315
+ "calendar": "Meta Business Suite",
316
+ "visibility": "public",
317
+ "copy": "Creator-approved caption",
318
+ "content_origin": "standalone",
319
+ "source_video_id": null,
320
+ "options": {"format": "reel", "share_to_facebook_story": false},
321
+ "first_comment": null,
322
+ "media": [{"path": "clip.mp4", "origin": "user-provided"}]
323
+ }
324
+ }
325
+ ```
326
+
327
+ For a derivative of a YouTube long video, set `content_origin: "youtube_long"`,
328
+ `source_video_id` to the exact 11-character source ID, and the approved first comment
329
+ to `🎥 Video completo en https://www.youtube.com/watch?v=SOURCE_ID`. That comment is
330
+ separate work after public visibility, never proof of completion at scheduling time.
331
+
332
+ The operator must supply a real, nonempty local evidence file for the named account.
333
+ Example `operator-pilot.json` below is deliberately disabled. Never change flags
334
+ based on these examples; only the operator's verified pilot can enable them.
335
+
336
+ ```json
337
+ {
338
+ "version": 1,
339
+ "platform": "facebook",
340
+ "account_id": "page-example",
341
+ "api_enabled": false,
342
+ "page_pilot_verified": false,
343
+ "evidence_ref": "operator-pilot-evidence.txt"
344
+ }
345
+ ```
346
+
347
+ For YouTube, use `platform: "youtube"`, the verified `youtube.channel_id`, calendar
348
+ `YouTube Studio`, and options such as `{"format":"video","title":"Approved title",
349
+ "selfDeclaredMadeForKids":false}` (the creator must decide the kids declaration).
350
+ The eventual visibility is `public`; the adapter uploads privately with the approved
351
+ future time. YouTube eligibility replaces `page_pilot_verified` with
352
+ `project_verified` and `studio_pilot_verified`, both false until operator verification.
353
+ New upload is the default; scheduling an existing video additionally requires its
354
+ exact `video_id` and truthful `never_published: true`. Apply the same preview and
355
+ approval commands above. Do not invent provider approval or assume upload acceptance
356
+ proves future publication.
357
+
358
+ After API submission, record fresh official-calendar evidence locally (for example
359
+ `calendar-evidence.png`, without tokens or signed links). Verify the exact account,
360
+ remote ID, copy, processing state and final time. Use the SHA-256 of that actual file.
361
+ `content_hash` comes from `intent.content_hash` in the saved preview; `approval_digest`
362
+ is the exact approved digest. `remote_ref` comes from the recorded remote attempt.
363
+ Example `calendar-observation.json` below uses placeholder hashes; it is not evidence:
364
+
365
+ ```json
366
+ {
367
+ "job_id": "example-reel",
368
+ "platform": "facebook",
369
+ "account_id": "page-example",
370
+ "content_hash": "REPLACE_WITH_PREVIEW_CONTENT_HASH",
371
+ "approval_digest": "REPLACE_WITH_APPROVED_DIGEST",
372
+ "remote_ref": "REPLACE_WITH_CONFIRMED_REMOTE_ID",
373
+ "publish_at": "2035-01-03T19:30:00+01:00",
374
+ "observed_at": "2035-01-01T12:05:00Z",
375
+ "source": "ui",
376
+ "calendar_visible": true,
377
+ "state": "native_scheduled",
378
+ "public_visible": false,
379
+ "processing_complete": false,
380
+ "evidence_path": "calendar-evidence.png",
381
+ "evidence_sha256": "REPLACE_WITH_ACTUAL_FILE_SHA256"
382
+ }
383
+ ```
384
+
385
+ Set the booleans/state to what was actually observed; never copy success from an
386
+ example. The observation must be newer than the job and any reconciliation barrier.
387
+ Keep the original media unchanged while reconciling. Then run:
388
+
389
+ socialcli native-schedule reconcile JOB_ID --observation calendar-observation.json --brand MyBrand
390
+ socialcli native-schedule status --brand MyBrand
391
+
392
+ Before rescheduling an exact-time job, refresh `native-calendar.json` with complete
393
+ coverage of the new local day. Preview, approval and the submit marker each require
394
+ fresh account-bound evidence; an old calendar cannot authorize moving into an
395
+ unobserved day. To change a verified schedule, show a new complete action preview with the local
396
+ ISO timestamp (including its correct offset for the stored timezone):
397
+
398
+ socialcli native-schedule reschedule JOB_ID --publish-at 2035-01-04T20:15:00+01:00 --brand MyBrand --dry-run
399
+
400
+ Only after approval, use the same command with `--approval-digest ACTION_DIGEST`
401
+ instead of `--dry-run`, then follow the approved official-UI handoff and reconciliation.
402
+ Both times remain reserved until fresh evidence confirms the change. Cancellation
403
+ also requires its own preview and approval; it does not authorize deleting the video.
404
+
405
+ Reconciliation reads local evidence and changes only the ledger; it does not publish.
406
+ Check the resulting `state` and notices: command completion alone does not establish
407
+ `native_scheduled`. A missing or conflicting observation stays unresolved. Preserve
408
+ the existing remote target; never create a second upload to repair it.
409
+
410
+ ## Check supplied media locally
411
+
412
+ socialcli media check POST_SLUG --brand MyBrand --json
413
+ socialcli media check POST_SLUG --brand MyBrand --only instagram --json
414
+
415
+ This checks ordinary `post.yml` routes without credentials, network requests,
416
+ uploads, copying or conversion. Shared files are inspected once. Read every
417
+ platform's `assets`, `issues` and `coverage`; a missing or unreadable file does
418
+ not discard other platform results. `passed` means the implemented local checks
419
+ passed, not that the provider will accept or publish the file. `partial` retains
420
+ passed platforms while others are blocked. Neither result approves publication.
421
+
422
+ Issue actions distinguish installing `ffprobe`, inspecting a timed-out file,
423
+ supplying compatible original media, configuring an account and reviewing post
424
+ text. Each ffprobe run is capped at30seconds and can open local files only.
425
+ The command does not download URLs or resolve remote hosting. Provider account
426
+ eligibility, TikTok creator-specific duration/privacy, public media URL availability
427
+ and full-file decoder integrity remain unchecked. Actual provider processing and
428
+ visibility still require readback after an explicitly approved operation.
429
+
430
+ Instagram image and Reel constraints are separate: the generic ordinary route
431
+ checks observed codec/container/frame rate plus its applicable dimensions and
432
+ size limits. TikTok's route also checks observed video codec/container/frame rate
433
+ and dimensions. For Facebook/YouTube the existing configured route policy is not
434
+ claimed to be a complete provider codec specification. Inspection metadata stays
435
+ outside durable approval payloads; supplied bytes are never modified. Native
436
+ manifests, Stories and group assets have their own validators and are outside
437
+ this command's scope. Continue to show the full publication dry-run for approval.
438
+
439
+ ## Read metrics and prepare engagement
440
+
441
+ socialcli stats --brand MyBrand --only youtube --timeout 120 --json
442
+ socialcli inbox --brand MyBrand --since yesterday --timezone America/New_York --timeout 120 --json
443
+
444
+ Set the external runner timeout longer than the command timeout. Keep stdout as one
445
+ JSON document and progress stderr separate. Aggregate partial results use exit 3
446
+ in rc2 (exit 1 in rc1). Inspect coverage and errors; a partial empty inbox never means no
447
+ comments. TikTok comments are unsupported. Missing metrics are unknown, not zero.
448
+ `--since` on stats filters publication dates, not the metrics reporting window.
449
+ For an offline brand report from already saved snapshots:
450
+
451
+ socialcli report --brand MyBrand --days 7 --top 5 --json
452
+ socialcli report --brand MyBrand --days 7 --as-of 2026-10-13 --only youtube
453
+
454
+ This does not refresh metrics or access accounts. It shows per-platform account
455
+ changes, an available previous daily window, top observed lifetime views and top
456
+ observed view changes. The actual observation timestamps and elapsed intervals
457
+ are included; daily files do not imply reads occurred at exactly the same hour.
458
+ A missing exact baseline is not replaced by another date. Missing values/items
459
+ never count as zero. New content absent from the baseline has no measured change.
460
+ Rankings cover observed items only, not a guaranteed complete account inventory.
461
+ Counter corrections and duplicate IDs remain explicit.
462
+
463
+ Account comparisons require saved matching account identity and dated observations.
464
+ Observations whose timestamp date differs from the daily file are not silently
465
+ assigned a timezone: the comparison stays unavailable. Existing snapshots do not
466
+ record the collector timezone, so legitimate midnight-boundary reads may need
467
+ explicit timezone provenance in a future format. Known values remain available.
468
+ Legacy snapshots lacking identity still provide known current values and lifetime
469
+ rankings, with partial status. Do not add identity retroactively from accounts.yml.
470
+ TikTok comparisons stay unavailable if its current response does not supply an
471
+ open_id; configured identity is not inferred as authenticated evidence. Failed,
472
+ stale and partial sources remain explicit, and exit1 may contain useful results.
473
+ The existing audience data has no audience-online-hours series: best posting time
474
+ is unavailable. Do not invent peak hours or causal recommendations from top posts.
475
+
476
+ For one supervised workflow across saved YouTube/Facebook/Instagram comments:
477
+
478
+ socialcli inbox-review --brand MyBrand --json
479
+ socialcli inbox-draft --file inbox-replies.yml --brand MyBrand --dry-run --timeout 120 --json
480
+
481
+ `inbox` reads owner replies from the platform and saves account/media/parent-bound
482
+ evidence, including after reinstalling or attaching an existing connection.
483
+ `inbox-review` is offline: it joins those observations to the actual reply journals.
484
+ `reply_state` is `observed`, `draft`, `blocked`, `uncertain` or `replied`.
485
+ `replied` can come from a verified local write or an observed platform reply by
486
+ the connected account. It does not recheck present visibility. Local uncertain
487
+ writes remain uncertain even if other owner replies are found.
488
+ `platform_reply_state` distinguishes `owner_reply_found`, `no_owner_reply_found`
489
+ and `unknown`. Unknown means incomplete or unverifiable evidence, never proof
490
+ that a comment is unanswered. A partial later scan retains positive evidence.
491
+ Former-account observations and proposals are excluded with explicit counts;
492
+ a foreign-brand journal still blocks review. An empty saved view never proves there
493
+ are no comments. Refresh with `inbox` first,
494
+ inspect its coverage, and preserve the selected account/comment identities.
495
+
496
+ The explicit reply file is:
497
+
498
+ ```yaml
499
+ version: 1
500
+ replies:
501
+ - platform: youtube
502
+ account_id: EXACT_CHANNEL_ID
503
+ comment_id: EXACT_TOP_LEVEL_COMMENT_ID
504
+ text: "Exact proposed reply"
505
+ - platform: facebook
506
+ account_id: EXACT_PAGE_ID
507
+ comment_id: EXACT_TOP_LEVEL_COMMENT_ID
508
+ text: "Exact proposed reply"
509
+ ```
510
+
511
+ Replace IDs with observed values; never infer an account. Instagram uses the same
512
+ schema with `platform: instagram`. Select 1–100 comments, each once. TikTok
513
+ comments remain unsupported. For a nested observation, explicitly provide the
514
+ verified top-level `reply_parent_id` as described under Resumable batches and
515
+ reply context; the preview shows the actual target without inventing deeper nesting.
516
+
517
+ Drafting checks every local selection before provider reads, rechecks author/text
518
+ and owner replies against the provider, and creates immutable child proposals plus one
519
+ community batch. It does not send replies. The complete batch preview is on stderr;
520
+ JSON stdout includes `batch.id`, `batch.fingerprint` and `prepared` child references.
521
+ If a later preparation fails, `status: partial`, `batch: null` and `prepared` retain
522
+ finished local drafts. Review them; no batch was approved or applied. Do not claim
523
+ that the unprepared comments were handled. An existing uncertain or verified reply
524
+ blocks a fresh unified draft. Resolve it before considering another response.
525
+ Incomplete owner-reply coverage also blocks preparation. Follow-ups in an already
526
+ answered thread require separate, deliberate review through the lower-level
527
+ proposal commands; never bypass the guard automatically. Two independent agents
528
+ can still race after preparation: this is read-backed prevention, not a provider
529
+ transaction or a cross-installation lock.
530
+
531
+ Show the full preview and obtain explicit approval of that exact fingerprint:
532
+
533
+ socialcli changes apply-batch BATCH_ID --approval APPROVED_DIGEST --brand MyBrand --json
534
+ socialcli changes status-batch BATCH_ID --brand MyBrand --json
535
+ socialcli changes reconcile-batch BATCH_ID --brand MyBrand --json
536
+ socialcli inbox-review --brand MyBrand --json
537
+
538
+ Reconciliation only reads existing attempts and never starts pending replies.
539
+ An interrupted apply reports the batch ID; inspect its status and child journals.
540
+ Meta inbox-bound replies retain their existing frequency limit and durable inbox
541
+ status. Partial batches are not cross-provider transactions and have no rollback.
542
+
543
+ Existing individual `comments draft/apply-draft` and YouTube community proposal
544
+ commands remain available. To group already reviewed child proposals, use
545
+ `changes prepare-batch --file children.yml --brand MyBrand --dry-run --json` with
546
+ `version: 1` and `children: [{provider: meta-comments, change_id: EXACT_UUID}]`.
547
+ The `children` reference file is different from the explicit `replies` file above.
548
+ Never automatically reply merely because an inbox read found a new comment.
549
+
550
+ ## Recovery and error actions
551
+
552
+ `account login --json` and `account status --json` return structured failure
553
+ documents with `error.code`, `error.action`, `error.retryable` and a fixed diagnostic.
554
+ `retry_later` applies to a safe read; `recover` resumes installation delivery;
555
+ `reconcile` checks an uncertain operation; `sign_in` concerns the installation;
556
+ `reconnect` requires explicit user authorization for that social account. A 409 or
557
+ network failure alone is never proof of revoked provider access. Unknown failures
558
+ require inspection, not automatic retries.
559
+
560
+ With explicit `--json`, `stats` and unified `inbox` preserve verified observations
561
+ and expose each diagnostic in `errors[]` with `code`, `action`, `retryable`,
562
+ `requires_user_action`, sanitized `message` and fixed `guidance`. Legacy comments
563
+ and YouTube community partial dictionaries retain items, cursors and flags and add
564
+ `error_detail`; do not assume they have the same envelope as `stats`. A missing
565
+ workspace also produces a structured error in these explicit JSON read modes.
566
+ `adjust_read` means reduce scope or adjust the budget, not repeat unchanged.
567
+ `unsupported` cannot be repaired by reconnecting. Generic authentication failure
568
+ requests account review; it does not prove revoked access. A token-refresh POST
569
+ with uncertain delivery must not become a safe read retry. Some legacy adapters
570
+ wrap typed causes, so their conservative fallback is inspection or account review.
571
+
572
+ Meta `comments apply/reconcile/status/apply-draft/reconcile-draft` and YouTube
573
+ `content youtube-community apply/reconcile/status` support explicit `--json`.
574
+ Keep stdout and stderr separate: the complete preview and exact fingerprint prompt
575
+ are on stderr; stdout is one result/error document. JSON does not approve anything.
576
+ Show the complete preview to the owner before supplying their approved fingerprint.
577
+ There is no `--yes` shortcut for these operations. EOF or a mismatched fingerprint
578
+ stops before the mutation service.
579
+
580
+ Successful results keep their existing fields. Nonverified durable results add
581
+ `error_detail`; inspect their status, IDs and journal even if a legacy reconcile
582
+ command exits 0. Failures before returning a durable result use `error`, plus
583
+ `change_id` when known. A failure after entering the apply service conservatively
584
+ requires reconciliation, including a failed journal save or interruption; it never
585
+ means the write was rejected. Use the corresponding reconcile/status command,
586
+ not a fresh proposal or another apply to retry a possibly completed write.
587
+
588
+ `publish --json` and `retry --json` also reserve stdout for one versioned document.
589
+ Full preview, progress and confirmation remain on stderr; a dry-run additionally
590
+ returns the exact `preview` string. Preserve both streams and show the owner the
591
+ complete preview. `--json` never grants approval; existing `--yes` still requires
592
+ prior explicit consent to that complete preview.
593
+
594
+ Publication reports include `status`, `resultados`, `errors` and `persistence`;
595
+ retry additionally reports `retry.selected` and `retry.blocked`. A report can be
596
+ `preview`, `cancelled`, `noop`, `ok`, `partial` or `error`. Each result retains its
597
+ original platform state, confirmed IDs, durable attempt IDs and visibility fields.
598
+ `error_detail` describes a failed/uncertain/pending media result;
599
+ `first_comment_error_detail` concerns only the comment, never a reason to upload
600
+ media again. Pending TikTok confirmation remains pending even with exit 0.
601
+ An unfinished first comment may likewise produce `partial` with the legacy exit 0.
602
+ Always inspect the document instead of inferring completion from the exit alone.
603
+
604
+ `persistence.complete` is true only after both resultado.json and history were
605
+ saved; false after an attempted publication whose persistence is incomplete, and
606
+ null before publication. Keep all returned observations after an interruption or
607
+ persistence failure. Confirmed results are not permission to replay an uncertain
608
+ platform; use the durable journal and provider observations for reconciliation.
609
+
610
+ Every `native-schedule` command accepts `--json`. It produces one versioned
611
+ `kind: native-schedule` envelope on stdout; preserve stderr for the complete local
612
+ preview. `data` contains approval documents or operation observations, `jobs`
613
+ contains the latest readable job snapshot, and `jobs_complete` says whether that
614
+ snapshot completed. An error can retain earlier jobs and confirmed IDs: preserve
615
+ them and check `errors` before acting. Checkpoint IDs are observations of an
616
+ existing upload, never proof of a public or scheduled post.
617
+
618
+ `status: ok` means the command completed, not that its jobs are public. Inspect
619
+ job/result `state` and `next_action`: prepared needs approval, waiting needs time,
620
+ UI handoff needs an authorized operator, and uncertain/verifying needs readback.
621
+ Native scheduling evidence does not establish publication. The original date,
622
+ timezone and approval document remain unchanged; JSON grants no new approval.
623
+
624
+ SSH executors must support the same JSON envelope. The client validates command,
625
+ brand, exit and safe output and never replays an invalid/lost remote response.
626
+ For an older executor, inspect authoritative state and upgrade or use legacy
627
+ read-only status; do not repeat a mutation to obtain a prettier response.
628
+ Parser errors (such as missing required CLI flags) remain standard CLI usage errors.
629
+
630
+ Typed failure codes now survive sanitized authentication, YouTube metadata/resource
631
+ and Meta management/comment wrappers. A provider GET timeout or rate limit can
632
+ request a bounded read retry; a lost token-refresh or SaaS token POST requires
633
+ reconciliation even when the outer command only requested a read. A generic
634
+ 401/403 does not prove expired authorization. Only a recognized service expiry
635
+ code requests reconnection, which still requires the owner. Partial comment and
636
+ community pagination, comments sync and the unified inbox retain verified rows
637
+ and the specific recovery action.
638
+
639
+ Other management commands are not all converted to this taxonomy; inspect
640
+ their exit code, stderr and durable result as well. No action hint grants approval
641
+ to reconnect, publish, delete or replay a write.
642
+
643
+ Meta management journals preserve sanitized errors, typed guidance, and request
644
+ stage/HTTP status when available. `write_intent` does not prove a request reached
645
+ Meta or a response arrived. Older journals cannot recover a discarded response.
646
+ Report `uncertain` as attempted and unconfirmed, never as applied. Reconcile the
647
+ existing ChangeSet with reads only; a fresh UUID cannot bypass an unresolved
648
+ operation on the same target.
649
+
650
+ Before a Meta Page profile update, every patch field must appear in the profile
651
+ read. Missing does not mean "currently not set". If Meta omits the field, the
652
+ change cannot be previewed or verified and is blocked before writing. Do not
653
+ silently map `bio` to `about` or another field; a different field needs its own
654
+ explicit preview and approval. A field missing during readback is unavailable
655
+ evidence, not proof of failure or success.
656
+
657
+ Revoking an installation is supported. Fine-grained, server-enforced permissions
658
+ such as “publish but never delete” are not implemented: the current client receives
659
+ provider tokens. Do not advertise local agent instructions as an access-control boundary.
660
+
661
+ Container checks require verified MOV/MP4 brand metadata. Ambiguous Matroska/WebM
662
+ identification is currently blocked even though TikTok supports WebM; supply an
663
+ existing MOV/MP4 or use a separately validated route. No automatic conversion.
664
+
665
+ ## Installation connection access
666
+
667
+ When the owner links an assistant, the browser lets them select its SocialCli
668
+ connections by platform. The service checks this selection before OAuth, attaching
669
+ an existing grant, migration, activation and token delivery. `account status --json`
670
+ can include `platform_access` from installation status. Existing installations
671
+ keep their previously approved access after the schema upgrade.
672
+
673
+ `platform_not_allowed` and `installation_permissions_invalid` require owner review;
674
+ never retry by silently reconnecting, choosing another brand or another installation.
675
+ The account page shows the selection. To change it, the owner revokes the old
676
+ installation and explicitly links a new one.
677
+
678
+ This is a connection-broker boundary. It does not narrow provider tokens, which may
679
+ carry permissions for linked accounts and additional actions. Per-action restrictions
680
+ remain unavailable. Previously delivered provider tokens can remain valid after
681
+ SocialCli revocation until they expire or are separately revoked at the provider.
682
+
683
+
684
+ ## Read diagnostics in Candidate 0.3.0rc3
685
+
686
+ - Aggregate `stats`, `inbox` and `report` JSON envelopes: exit 0 = ok,
687
+ exit 3 = partial, exit 1 = error, exit 2 = invalid command arguments.
688
+ Always inspect coverage and retain verified observations, including after errors.
689
+ A partial empty inbox never proves that no comments exist.
690
+ - `stats` and `inbox` default to a 300-second total budget. Give the process
691
+ additional time for persistence. Use `--only youtube` (repeat `--only` for more
692
+ platforms) to devote the read budget to selected providers. Increase `--timeout`
693
+ for large catalogs; the budget remains finite. `--since` on stats filters video
694
+ publication dates, not the analytics period. Inbox still examines older videos
695
+ because they can have new comments.
696
+ - Progress is summarized at most once every five seconds. YouTube audience is
697
+ requested before per-video retention and traffic enrichment. Unverifiable Meta
698
+ comments remain excluded and counted; more time cannot repair missing fields.
699
+ - YouTube inbox's authenticated moderation filter requires management consent.
700
+ `connect --management` and `--analytics` exist in rc1 too. Inspect the installed
701
+ `connect --help`. Only with owner authorization, use the connection example above
702
+ to add missing consent; reads never reconnect automatically. A single 403 can
703
+ also mean disabled comments or resource eligibility, not missing OAuth scopes.
704
+ - `local_configuration` indicates a local URL/proxy/CA setup issue, not server
705
+ rejection. `transport_failure` means connectivity failed. Preserve environment
706
+ proxies and credentials; correct configuration rather than bypassing it.
707
+ - In `delivery-options`, `native_schedule_authorization_verified` concerns native
708
+ scheduling only. The legacy `authorization_verified` alias has the same meaning,
709
+ identified by `authorization_verified_scope`. Neither verifies general read access.
710
+ - `credentials generate-key` writes a new external Fernet key privately, never to
711
+ stdout and never over an existing file. The parent must already be owned by you
712
+ with mode 0700; the file is created with mode 0600. Use a separate protected
713
+ directory per independent brand. The key is URL-safe base64 for 32 random bytes,
714
+ not a password or hexadecimal string.
715
+
716
+ ## Comment API corrections in Candidate 0.3.0rc3
717
+
718
+ YouTube comments with disabled discussion return `comments_disabled`, not a
719
+ reconnection request. Valid reply-list cursor traversal tolerates an omitted
720
+ total count while retaining supplied-data consistency checks. Meta may withhold
721
+ a comment author; read observations retain `author_id: null`. Do not invent the
722
+ author or draft replies from that unverified identity. Facebook verification can
723
+ resolve a receipt/list ID difference using exact provider comment permalinks and
724
+ owned-target membership; unknown outcomes must still never be resent.
725
+
726
+
727
+ ## Explicit approval and preflight
728
+
729
+ The approval path is: display the complete preview, obtain the creator's explicit
730
+ approval, then pass its exact fingerprint to the execution command. Supplying a
731
+ flag is the transport for that approval; it does not grant an agent permission.
732
+
733
+ ```sh
734
+ socialcli publish clip --brand Example --only facebook --dry-run
735
+ # After the creator approves that complete preview:
736
+ socialcli publish clip --brand Example --only facebook --approval-fingerprint APPROVED_DIGEST
737
+
738
+ socialcli schedule clip --at 2030-01-01T12:00:00Z --brand Example --only facebook --dry-run
739
+ # After the creator approves the content and exact scheduled time:
740
+ socialcli schedule clip --at 2030-01-01T12:00:00Z --brand Example --only facebook --approval-fingerprint APPROVED_DIGEST
741
+
742
+ socialcli content youtube-owned prepare --file change.yml --brand Example --dry-run
743
+ # Keep the returned ChangeSet ID and its fingerprint together.
744
+ socialcli content youtube-owned status CHANGE_ID --brand Example
745
+ socialcli content youtube-owned apply CHANGE_ID --brand Example --approval-fingerprint APPROVED_FINGERPRINT
746
+ ```
747
+
748
+ `--approval` and `--approval-digest` are aliases for `--approval-fingerprint` on
749
+ these commands and on the management apply commands. The same options are
750
+ available on `changes apply`, `meta apply`, `comments apply`, `comments apply-draft`,
751
+ `content youtube-assets apply`, `content youtube-community apply`,
752
+ `content youtube-analytics apply`, `story apply` and `group handoff`.
753
+ Check each installed command's help; native scheduling has its existing explicit
754
+ `--approval-digest` contract.
755
+
756
+ Management proposals are durable and include a proposal identity and observations.
757
+ Preparing again can produce a new ID and fingerprint even for the same input
758
+ file. Preserve the exact returned pair, inspect that stored proposal, and approve
759
+ that fingerprint. Do not substitute a digest from a different preparation.
760
+ Publication digests bind the selected account, content and media bytes; queue
761
+ digests additionally bind the scheduled instant. Changed inputs require a fresh
762
+ complete preview and approval. Execution rechecks before writing.
763
+
764
+ Without a terminal or explicit approval, these commands fail promptly without
765
+ reading an open input pipe. To deliberately provide approval through stdin:
766
+
767
+ ```sh
768
+ printf '%s\n' "$APPROVED_FINGERPRINT" | socialcli content youtube-owned apply CHANGE_ID --brand Example --approval-stdin
769
+ ```
770
+
771
+ `--approval-stdin` reads one line and can wait for that line by explicit choice.
772
+ It cannot be combined with a fingerprint argument. Do not blindly pipe a newly
773
+ generated digest into an apply command: the creator must review and approve the
774
+ complete preview first. On an interactive terminal, the existing prompts remain.
775
+
776
+ For `publish` and `schedule`, the previously authorized `--yes` workflow remains
777
+ supported, including displaying the complete preview and checking for changes.
778
+ For management commands that historically required an exact fingerprint,
779
+ `--yes` still cannot replace it. A supplied wrong fingerprint is never overridden
780
+ by `--yes`. Local schedule output is text; `publish --json` keeps its complete
781
+ preview and approval UI on stderr and returns one JSON document on stdout.
782
+
783
+ ### Combined read-only preflight
784
+
785
+ `preflight` is an alias of `doctor`. Both accept explicit supplied files and/or a
786
+ post slug, and collect local validation alongside account identity checks:
787
+
788
+ ```sh
789
+ socialcli doctor --brand Example --platform youtube --post clip --media /path/to/supplied.jpeg --json
790
+ socialcli preflight --brand Example --platform youtube --require-scope https://www.googleapis.com/auth/youtube.force-ssl --json
791
+ ```
792
+
793
+ `--media` is repeatable and inspects existing files without modifying them.
794
+ `--post` reuses `media check` and preserves each platform's diagnostics when
795
+ another supplied file is invalid. Account and credential-store diagnostics remain
796
+ in the report alongside media failures. Encrypted-store checks validate the
797
+ protected key and existing store without creating lock files or repairing state.
798
+ A directory with owner permissions 0700 and no group/other access is accepted,
799
+ including mode 2700; credential files still require mode 0600.
800
+
801
+ Scope checks compare exact scopes recorded as granted in the token response;
802
+ they never substitute requested OAuth scopes or the capability matrix. Unknown
803
+ scope grants remain unknown and cannot satisfy `--require-scope`, which requires
804
+ an explicit `--platform`. Identity verification and recorded scopes do not prove
805
+ write permissions or provider approval. No token refresh, reconnection, remote
806
+ mutation, publication, media generation or reencoding is performed. TikTok
807
+ identity checks compare the authenticated provider `open_id` with the configured
808
+ account using a read-only request; they do not verify publishing permission.
809
+ Without `--media` or `--post`, file integrity is not
810
+ checked. A supplied image passing inspection alone does not prove eligibility
811
+ for a particular publishing route; use `--post` for route validation.
812
+
813
+
814
+ ## Resumable batches and reply context
815
+
816
+ Community batches remain sequential, resumable operations, not atomic cross-provider transactions. Each child must verify before the next starts. A received comment receipt now permits one additional bounded, read-only reconciliation during apply, covering delayed provider readback without another POST. A lost receipt, incomplete evidence or contradictory identity still stops the batch. Reconciliation never starts pending children.
817
+
818
+ Batch JSON preserves existing keys and adds all children to `outcomes`, including `not_attempted`, with child index, provider, platform and change ID. `summary` includes total and verified/pending/uncertain/blocked counts and ID lists. A partial batch with only untouched children reports `batch_pending` and `action: apply_remaining`; an uncertain child reports `operation_uncertain` and `action: reconcile`. Continue the same approved batch with its exact fingerprint, retaining all verified outcomes. Progress is on stderr; stdout contains one JSON document.
819
+
820
+ `inbox-draft` errors include `failed.child_index` (one-based), platform, account/comment IDs, stage and a safe reason. Completed immutable children remain in `prepared` after a later failure. Diagnostics do not reproduce arbitrary provider or storage exception text.
821
+
822
+ Facebook reply preparation accepts an unavailable external comment author when exact comment membership and media ownership can be verified. The author remains null in the approved baseline. Changes to text, identity or membership block the write. Own-comment edit/delete and created-reply verification still require the authenticated account's exact author ID.
823
+
824
+ A nested observation can explicitly map a new thread reply to its observed top-level parent:
825
+
826
+ ```yaml
827
+ version: 1
828
+ replies:
829
+ - platform: youtube
830
+ account_id: fictional-channel
831
+ comment_id: observed-reply-id
832
+ reply_parent_id: observed-top-level-comment-id
833
+ text: Exact supplied reply text
834
+ ```
835
+
836
+ The source and mapped parent must both be observed for the same account/media/thread. The parent must be top-level and satisfy the existing eligibility rules. Source membership/text/author are read again during preparation and immediately before the write. The full preview binds `before.reply_context` and the actual write parent. This is supported through the existing YouTube, Facebook and Instagram reply edges; it does not invent a deeper provider nesting relationship or imply a mention. Multiple selections mapping to the same parent are rejected. Existing replied/uncertain parent protections remain in effect. Inbox review links the resulting journal to both the parent and selected source observation.
837
+ ## Add another social platform without signing out
838
+
839
+ If `connect` reports `platform_not_allowed`, run
840
+ `socialcli account permissions --brand MyBrand --no-browser --json`.
841
+ Give its `browser_url` directly to the owner; never open or approve it for them.
842
+ The owner adds platforms to this installation in their SocialCli browser account.
843
+ The command only reads status and provides the link; it does not grant access.
844
+ After confirmation, check `socialcli account status --brand MyBrand --json`
845
+ and run `socialcli connect PLATFORM --brand MyBrand --no-browser`.
846
+ Existing YouTube and other connections remain active. Do not log out, revoke the
847
+ assistant or disconnect existing accounts just to add a network. If the service
848
+ does not support expansion yet, keep the connections and report the required
849
+ service update. Platform access does not replace provider consent or publication
850
+ approval.