social-poster
Skill17 starsChanged 2 months agoMITView on GitHub
Direct OAuth posting to social media — no Docker, no database, no redirect URI whitelisting. Generates OAuth URLs, exchanges user codes/PINs, stores tokens in a local vault, and posts via direct platform API calls. Supports 15 platforms across OAuth, webhook, and token-based auth.
- Reads credentials
2 other projects use a version of this.
Adopted by
Most recently taken up by
- ActuallyNodejam/openpaperworktheir own version, first seen Nov 8, 2026
- Ivan-n-Beliaev/claude-skillstheir own version, first seen Oct 17, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- ActuallyNodejam/openpaperwork★ 0same name
- Ivan-n-Beliaev/claude-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository18 other files it gives its agents.
- 3dprinterSkill
- ai-projectsSkill
- ciberjohn-no-slopSkill
- excalidrawSkill
- gymcoachSkill
- jevSkill
- joplin-brainSkill
- medium-storySkill
- openrouter-image-genSkill
- openscad-cadSkill
- paperclip-creatorSkill
- proxmox-estateSkill
- short-videosSkill
- skill-writerSkill
- t3mp3st-autonomous-securitySkill
- technical-trainerSkill
- transcribeSkill
- uk-business-consultantSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 5 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 60
Run python3 ~/.social-poster/social-poster.py vault:status to check which platforms have stored tokens
- Distinctive: line 83
Check the vault that the requested platforms have tokens
- Distinctive: line 84
Use the platform-specific API to post
- Distinctive: line 91
Create a Hermes cron job that calls the posting function at the specified time
- Very specific: line 92
Use cronjob action='create' with the posting instructions
- Very specific: line 139
All platforms support environment variable fallback when config.json is absent.
- Distinctive: line 174
When the agent is on a remote machine and the user is on a different machine (e.g., their desktop "Kratos"), OAuth redirect URLs need to …
- Distinctive: line 218
When posting, respect per-platform limits:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Social Poster — Direct OAuth Posting
Overview
| Attribute | Value |
| Total platforms | 14 |
| OAuth platforms | X, LinkedIn, Instagram, Mastodon, Twitch, Reddit, Threads, Facebook, YouTube |
| Webhook/Token | Discord, Slack, Telegram, GitHub |
| Direct | Bluesky |
| Auth | OAuth 1.0a, OAuth 2.0, App Passwords, Webhooks, PATs |
User Interaction Flow
1. "What networks can you post to?"
The agent should:
- Run
python3 ~/.social-poster/social-poster.py vault:statusto check which platforms have stored tokens - Report the full list of 14 platforms, marking connected ones with ✅ and the rest as ⬜
- Offer: "Want me to guide you through setting up any of them?"
Example response:
14 platforms supported. Currently connected:
✅ X, LinkedIn, Bluesky
⬜ Instagram, Mastodon, Twitch, Reddit, Discord, Slack, Telegram, GitHub, Threads, Facebook, YouTube
Say 'connect [platform]' and I'll walk you through it."2. "Connect [platform]" or "How do I set up [platform]?"
The agent should:
- Load the platform's setup guide from
references/platform-setup-guides.md - Walk the user through the numbered steps, one at a time
- For OAuth platforms: ask for credentials, save to config.json, generate auth URL, exchange code
- For webhook/token platforms: ask for the URL/token, save to config.json, verify it works
3. "Post this to [platforms]: [content]"
The agent should:
- Check the vault that the requested platforms have tokens
- Use the platform-specific API to post
- Report success/failure per platform
- Respect per-platform character limits (see Platform Character Limits below)
4. "Schedule this for [time]"
The agent should:
- Create a Hermes cron job that calls the posting function at the specified time
- Use
cronjob action='create'with the posting instructions
Architecture
{{SOCIAL_POSTER_DIR}}/
├── config.json — API credentials (key/secret per platform)
├── vault.json — User access/refresh tokens
├── social-poster.py — Unified CLI (vault status, auth URL gen, token exchange)
├── webhook-poster.py — Webhook/Token poster (Discord, Slack, Telegram, GitHub)
├── auth-x-step1.py — X OAuth step 1 (request token + auth URL)
├── auth-x-step2.py — X OAuth step 2 (exchange PIN for access token)
├── oauth-callback-server.py — Callback HTTP server (Tailscale Serve)
└── last_code.txt — Latest OAuth code captured (auto-generated)Platform Setup Guides
For step-by-step instructions on creating developer apps and getting credentials,
load references/platform-setup-guides.md. Each platform has:
- Exact URLs to visit
- Buttons to click and fields to fill
- Where to find credentials
- How the auth flow works
Quick Reference: Auth Method Summary
| Platform | Auth Method | What User Needs | Config Keys |
| X | OAuth 1.0a PIN | API Key + Secret | x.api_key, x.api_secret |
| OAuth 2.0 | Client ID + Secret | linkedin.client_id, linkedin.client_secret | |
| Bluesky | App password | Handle + App Password | bluesky.handle, bluesky.app_password |
| OAuth 2.0 | App ID + Secret | instagram.app_id, instagram.app_secret | |
| Mastodon | OAuth 2.0 | Instance + Client ID/Secret | mastodon.instance, mastodon.client_id, mastodon.client_secret |
| Twitch | OAuth 2.0 | Client ID + Secret | twitch.client_id, twitch.client_secret |
| OAuth 2.0 | Client ID + Secret | reddit.client_id, reddit.client_secret | |
| Threads | OAuth 2.0 | App ID + Secret | threads.app_id, threads.app_secret |
| OAuth 2.0 | App ID + Secret | facebook.app_id, facebook.app_secret | |
| YouTube | OAuth 2.0 | Client ID + Secret | youtube.client_id, youtube.client_secret |
| Discord | Webhook | Webhook URL | discord.webhook_url |
| Slack | Webhook | Webhook URL | slack.webhook_url |
| Telegram | Bot token | Token + Chat ID | telegram.bot_token, telegram.chat_id |
| GitHub | PAT | PAT + Repo | github.pat, github.repo |
Environment Variables
All platforms support environment variable fallback when config.json is absent.
This is useful for CI/CD or ephemeral environments:
| Env Var | Config Key |
X_API_KEY | x.api_key |
X_API_SECRET | x.api_secret |
LINKEDIN_CLIENT_ID | linkedin.client_id |
LINKEDIN_CLIENT_SECRET | linkedin.client_secret |
INSTAGRAM_APP_ID | instagram.app_id |
INSTAGRAM_APP_SECRET | instagram.app_secret |
BLUESKY_HANDLE | bluesky.handle |
BLUESKY_APP_PASSWORD | bluesky.app_password |
MASTODON_INSTANCE | mastodon.instance |
MASTODON_CLIENT_ID | mastodon.client_id |
MASTODON_CLIENT_SECRET | mastodon.client_secret |
TWITCH_CLIENT_ID | twitch.client_id |
TWITCH_CLIENT_SECRET | twitch.client_secret |
REDDIT_CLIENT_ID | reddit.client_id |
REDDIT_CLIENT_SECRET | reddit.client_secret |
THREADS_APP_ID | threads.app_id |
THREADS_APP_SECRET | threads.app_secret |
FACEBOOK_APP_ID | facebook.app_id |
FACEBOOK_APP_SECRET | facebook.app_secret |
YOUTUBE_CLIENT_ID | youtube.client_id |
YOUTUBE_CLIENT_SECRET | youtube.client_secret |
DISCORD_WEBHOOK_URL | discord.webhook_url |
SLACK_WEBHOOK_URL | slack.webhook_url |
TELEGRAM_BOT_TOKEN | telegram.bot_token |
TELEGRAM_CHAT_ID | telegram.chat_id |
GITHUB_PAT | github.pat |
GITHUB_REPO | github.repo |
Cross-Machine OAuth (Reverse Proxy Required)
When the agent is on a remote machine and the user is on a different machine (e.g., their desktop "Kratos"), OAuth redirect URLs need to point somewhere the user's browser can reach.
You need a reverse proxy that makes a local port accessible via HTTPS at a URL you can reach from your browser. Options include:
- Tailscale Serve — HTTPS on your tailnet, no public internet needed
- Cloudflare Tunnel (
cloudflared) — public or private tunnels - Nginx Proxy Manager — if you have a domain pointing to your VPS
- ngrok — quick public URL for testing
- Your VPS hostname — if it already has HTTPS
The URL does NOT need to be public on the internet — it just needs to be reachable by YOUR browser.
Setup (one-time)
# 1. Start the callback server (persistent, port 19876)
python3 ~/.social-poster/oauth-callback-server.py 19876 &
# 2. Use your reverse proxy to route paths to 127.0.0.1:19876
# Example with Tailscale: tailscale serve --bg --set-path /oauth-callback 19876
# Example with Cloudflare: cloudflared tunnel --url http://localhost:19876
# Example with nginx: proxy_pass http://127.0.0.1:19876 in your site config
# The paths you need depend on which platforms you use:
# - /oauth-callback # For Mastodon, Twitch, Reddit, etc.
# - /integrations/social/linkedin # If using LinkedIn
# - /integrations/social/instagram # If using InstagramHow the Callback Server Works
The Python HTTP server (oauth-callback-server.py) listens on 127.0.0.1:19876.
Your reverse proxy forwards HTTPS requests to it. When an OAuth redirect arrives
with ?code=..., the server saves the code to {{SOCIAL_POSTER_DIR}}/last_code.txt.
The agent then reads the code and exchanges it for tokens. The server stays alive between sessions and handles any number of callbacks.
Platform Character Limits
When posting, respect per-platform limits:
| Platform | Max Length | Notes |
| X/Twitter (non-premium) | 280 chars | Requires who_can_reply_post: everyone |
| X/Twitter (premium) | 25,000 chars | Same |
| Bluesky | 300 chars | Returns 400 if exceeded |
| 3,000 chars | Professional tone | |
| 63,206 chars | Link for best reach | |
| 2,200 chars | Image/video required | |
| Threads | 500 chars | Short and punchy |
| YouTube | 5,000 chars | Description only |
| Mastodon | 500 chars | Instance-dependent |
| Twitch | N/A | Channel announcements |
| 40,000 chars | Title max 300 | |
| Discord | 2,000 chars | Embeds supported |
| Slack | 40,000 chars | Block kit |
| Telegram | 4,096 chars | HTML/Markdown |
| GitHub | N/A | Issues/gists |
Pitfalls
- Never use
os.path.expanduser("~")directly in Hermes sessions — it resolves to the sandbox home. Scripts auto-detect and usepwd.getpwuid(). - config.json and vault.json must be chmod 600 — NEVER committed to git.
- LinkedIn scopes must NOT include org scopes unless the app has Community Management API.
- LinkedIn MUST NOT use
prompt=nonefor first-time authorization. - X/Twitter callback must be
oobfor PIN-based flow. - Instagram app MUST be Live (not Development) for OAuth.
- Bluesky uses app passwords (format:
xxxx-xxxx-xxxx-xxxx), not regular passwords. - Redirect URI MUST match exactly between auth URL and token exchange POST.
stateparameter is REQUIRED for all OAuth 2.0 flows — built into all generators.post.pyis text-only — any post carrying a picture needsscripts/post_with_image.py(same vault/config, per-platform copy from<dir>/<platform>.md). Verified upload recipes for X, LinkedIn, Bluesky, Mastodon and Discord are inreferences/image-posts.md.- LinkedIn
/rest/postsreturns 500 for every active version on this app (202601-202608, 2026-09-15); post images via/v2/assets?action=registerUpload+/v2/ugcPostswithshareMediaCategory: IMAGE. Aurn:li:image:URN from/rest/imagesin a ugcPost fails as "not owned by the author". - X returns 402
credits depletedregardless of code correctness once the Pay-Per-Use balance hits zero. Auth and media upload still succeed, so diagnose in that order before touching the code. - Platform setup guides may drift as developer portals update their UIs. If a user reports a step doesn't match, verify at the official developer portal URL and update the guide.
- Discord's edge returns 403 to urllib's default User-Agent (
Python-urllib/3.x) while accepting any explicit one. A health probe that sends noUser-Agenttherefore reports a perfectly live webhook as "deleted/invalid" and sends you off to recreate something that was never broken.post.pysendsUser-Agent: SocialPoster/1.0and works fine. Set an explicit agent on every Discord-touching request, and before acting on a discordFAIL, confirm with an explicit-agent GET or a real POST rather than trusting the 403. - Store a Discord webhook URL without
?wait=true.post_discordtreats any non-empty response body as failure, so?wait=true(which makes Discord return the created message object instead of an empty 204) turns every successful post into a reported failure. If a Discord result looks like a failure, read the channel back before believing it. - A token can expire with no refresh path. An OAuth app may return an access token with a long lifetime and an empty
refresh_tokenif programmatic refresh is not enabled on it. Record the expiry timestamp when you store the token and watch it, or the platform dies silently on a known date. A daily platform-state watchdog that alerts on change (including days-remaining) turns that into an advance warning.
References
references/platform-setup-guides.md— Step-by-step setup guides for all 15 platformsreferences/platform-expansion.md— Full roadmap and implementation notesreferences/oauth-flows.md— OAuth implementation details and token exchange reference
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

