원클릭으로
post-to-linkedin
// Use when announcing a release, paper, grant, talk, award, or project update on LinkedIn from this repo. Covers drafting, OAuth setup, token refresh, and posting via scripts/post_to_linkedin.py.
// Use when announcing a release, paper, grant, talk, award, or project update on LinkedIn from this repo. Covers drafting, OAuth setup, token refresh, and posting via scripts/post_to_linkedin.py.
Systematic web search for news, media, policy, and industry coverage of FORTIS Lab publications, tools, and research. Use when the user asks for a news audit, media coverage check, broader impact evidence, or visibility search for their work.
Systematic bibliometric audit of citing-paper author affiliations across OpenAlex and Dimensions Analytics. Use when the user wants to find which notable institutions (government agencies, foundation model companies, national labs, Big Tech, finance, pharma) have authors who cite their published work. Pair with the news-search skill for full external-impact evidence; this skill is the bibliometric half, news-search is the editorial-coverage half. Also use for legacy prompts: "citation audit", "affiliation audit", "citation-affiliation audit", or "Dimension 9 audit" (this skill was previously Dimension 9 inside news-search; split out 2026-05).
Add or update content that must appear on both the website and the LaTeX CV. Use when the user mentions adding a new paper, award, grant, service role, teaching course, PhD student, open-source project, or any other content that overlaps between the website and CV.
Use when announcing a release, paper, grant, talk, award, or project update on X (Twitter) from this repo. Covers drafting, style compliance, cost preview, and posting via scripts/post_to_x.py.
Create 1-page or 2-page CV variants from a longer master LaTeX CV. Use when the user asks for a short CV, concise CV, one-page CV, two-page CV, resume-style version, or a role-specific application CV derived from `cv/cv-full.tex` or another master CV source.
| name | post-to-linkedin |
| description | Use when announcing a release, paper, grant, talk, award, or project update on LinkedIn from this repo. Covers drafting, OAuth setup, token refresh, and posting via scripts/post_to_linkedin.py. |
Compose and post announcements to LinkedIn from this repo via scripts/post_to_linkedin.py. LinkedIn API posts are free (no per-post charge, unlike X Premium). Max commentary length is 3,000 chars.
The draft format, hashtag sets, and writing rules are shared with the post-to-x skill (same scripts/drafts/ directory, same references/draft-patterns.md, same references/hashtag-sets.md at skills/post-to-x/references/).
Do not use for: generic networking DMs, connection requests, group posts to a Company Page feed (this skill posts to the authenticated user's personal feed only).
Two gates.
Gate 1: drafting / previewing
~/miniforge3/envs/py312/python.exe has requests and python-dotenv installed (pip install -r scripts/requirements-post.txt).data/open-source.json, data/publications.json, or user-provided text.Gate 2: real post
.env at repo root contains LINKEDIN_CLIENT_ID + LINKEDIN_CLIENT_SECRET.w_member_social) and Sign In with LinkedIn using OpenID Connect (Standard Tier, gives openid profile email) approved. Both are self-serve; approval is typically instant..env also contains LINKEDIN_ACCESS_TOKEN, LINKEDIN_TOKEN_EXPIRES_AT, LINKEDIN_USER_URN, written by --auth on first run. LINKEDIN_REFRESH_TOKEN is written when LinkedIn returns one; on default self-serve apps it may be empty, in which case re-run --auth after the access token expires (60 days).If Gate 2 fails, stop and report before posting. Drafting and previewing may continue.
Before the first post, user must:
http://localhost:8765/callback to Authorized redirect URLs..env (see .env.example).python scripts/post_to_linkedin.py --auth
This opens a browser, user authorizes, tokens + user URN are written back to .env. Takes about 10 seconds.Access tokens last 60 days. The script auto-refreshes when a refresh token is available, but LinkedIn's default self-serve tier typically does not return a refresh token (observed 2026-04-22). In that case, re-run --auth when the access token expires; the re-auth flow is unchanged, takes ~10 seconds.
1. Identify source → open-source.json / publications.json / user-provided
2. Draft to file → scripts/drafts/<slug>.md (same directory as X drafts)
Can reuse an X draft directly, or tweak for LinkedIn tone.
For LinkedIn the draft can be longer (up to 3,000 chars)
and more narrative; hashtag volume can be higher (~5 to 10
is normal on LinkedIn without penalty).
3. Preview dry-run → python scripts/post_to_linkedin.py --dry-run --draft <path>
4. Iterate → show preview, accept user edits, re-preview
5. Post → python scripts/post_to_linkedin.py --yes --draft <path>
Attach images with --media <path> (repeatable, up to 20).
6. Report URL → return https://www.linkedin.com/feed/update/<URN>/
Always run step 3 before step 5. No exceptions. --yes skips the interactive prompt; never use --yes without a prior successful --dry-run that the user approved.
skills/post-to-x/references/hashtag-sets.md.it is) over contractions.U+202F.data/open-source.json, data/publications.json, or user-stated facts.| Aspect | X (Premium) | |
|---|---|---|
| Char limit | 25,000 | 3,000 |
| Per-post cost | $0.20 with URL, $0.015 without | free |
| URL length counting | t.co shortens to 23 chars | full literal length |
| Hashtags | 3 to 5 | 3 to 10 |
| Tone | direct, punchy, dev-adjacent | professional, narrative, network-adjacent |
| Thread support | native reply-chain (X side only) | not used; single post is canonical on LinkedIn |
| Image limit per post | 4 | 20 |
| Mistake | Fix |
|---|---|
| Reusing an X draft verbatim without adjusting tone | LinkedIn readers expect more context; add 1-2 sentences of positioning before the bullets |
| Dumping too many hashtags (over 10) | LinkedIn allows more than X but the algorithm still penalizes stuffing; stay at 5 to 8 |
Posting without running --dry-run | Contract: dry-run is mandatory. Check cost, char count, media attachments |
| Posting with expired token + no refresh_token | Re-run --auth to get a fresh refresh_token |
| Posting an X draft that uses t.co-shortened URL math | LinkedIn counts URLs literally; re-check char count on the preview |
scripts/post_to_linkedin.pypython scripts/post_to_linkedin.py --help.env.example for LINKEDIN_* keysskills/post-to-x/references/draft-patterns.mdskills/post-to-x/references/hashtag-sets.md