AI ์์ด์ ํธ๊ฐ LinkedIn์ ๋ค๋ฃจ๋ CLI โ ๋น๊ณต์ ์ฝ๊ธฐ์ ๊ณต์ OAuth ๊ฒ์๋ฅผ ๋ช ํํ ๋ถ๋ฆฌ
ํ๊ตญ์ด ยท English
linkedin-cli๋ ๋ ํ๋ฉด์ ๋ช
ํํ ๋ถ๋ฆฌํฉ๋๋ค.
read.*: ๋ณธ์ธ LinkedIn ์น ์ธ์ ์ ์ฌ์ฉํ๋ ๋น๊ณต์ ์ฝ๊ธฐ ์ํฌํ๋ก์ฐpost.*: LinkedIn OAuth์ ๊ณต์ LinkedIn API๋ฅผ ์ฌ์ฉํ๋ ๊ณต์ ์ฐ๊ธฐ ์ํฌํ๋ก์ฐ
ํ๊ทธ: linkedin, cli, sns-json-v1, unofficial-read, official-post, personal-workflow, oauth, comments, reactions, media
์ด ํ๋ก์ ํธ๋ LinkedIn๊ณผ ๋ฌด๊ดํฉ๋๋ค. ์ฝ๊ธฐ ๋ช ๋ น์ ๋น๊ณต์ ์น ๋์์ ์์กดํ๋ฉฐ, LinkedIn ๋ด๋ถ ์๋ํฌ์ธํธ๊ฐ ๋ฐ๋๋ฉด ๊นจ์ง ์ ์์ต๋๋ค. ๊ณ์ ์ ์ ์ฉ๋๋ ์ฝ๊ด์ ์ฌ์ฉ์๊ฐ ์ง์ ๊ฒํ ํด์ผ ํ๋ฉฐ, ์ค์ ์ฑ ์์ ์ฌ์ฉ์์๊ฒ ์์ต๋๋ค.
์ฝ๊ธฐ:
- ํ ํผ๋ ์ฝ๊ธฐ
- ์ ์ฅํ ๊ฒ์๊ธ ์ฝ๊ธฐ
- ํ๋กํ ์กฐํ
- ์ฌ๋/๊ฒ์๊ธ ๊ฒ์
- ํน์ ํ๋กํ์ ๊ฒ์๊ธ ์กฐํ
- ๋จ์ผ activity ์กฐํ
- ํ๋ ๋๊ธ ์ฝ๊ธฐ
- ํ๋ ๋ฐ์ ์ฝ๊ธฐ
- ์์ด์ ํธ, ์คํฌ๋ฆฝํธ, SNS CLI ecosystem ์๋น์ฉ
sns-json-v1JSON ์ถ๋ ฅ
์ฐ๊ธฐ:
- ์ค์ ๋ฐํ ์ ๊ณต์ ๊ฒ์ payload dry-run
- ๊ณต์ LinkedIn Posts API๋ก ํ ์คํธ ๊ฒ์
- LinkedIn Images + Posts API๋ก ๋ก์ปฌ ์ด๋ฏธ์ง 1๊ฐ ๊ฒ์
- ๋ก์ปฌ ์ด๋ฏธ์ง 2~20์ฅ ๋ค์ค ์ด๋ฏธ์ง ๊ฒ์
- LinkedIn Videos + Posts API๋ก ๋ก์ปฌ MP4 ์์ 1๊ฐ ๊ฒ์
- LinkedIn Documents + Posts API๋ก PDF/DOC/DOCX/PPT/PPTX ๋ฌธ์ ๊ฒ์
- Posts API๋ก non-sponsored poll ๊ฒ์
- article/link ๊ฒ์
- ๊ธฐ์กด ๊ฒ์๊ธ ์ฌ๊ณต์
- ๊ณต์ Comments API ๊ธฐ๋ฐ
post reply๋ต๊ธ ์์ฑ - ๊ฒ์๊ธ commentary ์์
- ๊ณต์ Comments API๋ก ๋๊ธ ๋ชฉ๋ก/์กฐํ/์์ฑ/์์ /์ญ์
- ๊ณต์ Reactions API๋ก ๋ฐ์ ๋ชฉ๋ก/์กฐํ/์์ฑ/์ญ์
- ๊ณต์ Social Metadata API๋ก ๋ฐ์/๋๊ธ ์์ฝ ์กฐํ ๋ฐ ๋๊ธ open/closed ์ํ ์์
- Social Metadata API ๊ฒฐ๊ณผ๋ฅผ
insights.media๊ณ์ฝ์ผ๋ก ์กฐํ - Organization Share Statistics API ๊ฒฐ๊ณผ๋ฅผ
insights.organization๊ณ์ฝ์ผ๋ก ์กฐํ - ๊ฐ์ธ ๊ณ์ ๋จ์
insights.user๋ ํ์ฌunsupported๊ณ์ฝ์ผ๋ก ๋ช ์ - ํ ํฐ์ ํ์ํ read ๊ถํ์ด ์์ ๋ ๋จ์ผ ๊ฒ์๊ธ ์กฐํ ๋ฐ author๋ณ ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ
- share/ugcPost URN, numeric share id, feed update URL๋ก ๋ณธ์ธ ๊ณต์ ๊ฒ์๊ธ ์ญ์
- ์ ์ฅํ ๊ฒ์๊ธ ์ ์ฅ ์ทจ์
- react, unreact, save, unsave, comment, ๊ตฌํ posting์ ์ํ legacy browser fallback ์ ์ง
pip install agent-linkedin
# ๋๋
uv tool install agent-linkedinagent-linkedin ํจํค์ง๊ฐ linkedin-cli ๋ช
๋ น์ ์ ๊ณตํฉ๋๋ค. PyPI์์ linkedin-cli ์ด๋ฆ์ ์ด๋ฏธ ์ ์ ๋์ด ๋ฐฐํฌ๋ช
๋ง ๋ค๋ฆ
๋๋ค.
์์ค์์ ์ค์น:
git clone https://github.com/ai-native-engineer/linkedin-cli.git
cd linkedin-cli
uv sync --extra dev์์ค์์ ์คํํ ๋๋ ๋ช
๋ น ์์ uv run์ ๋ถ์
๋๋ค(์: uv run linkedin-cli --help). uv tool install .๋ก ์ ์ญ ์ค์นํ๋ฉด ์๋ ์์์ฒ๋ผ linkedin-cli๋ฅผ ๊ทธ๋๋ก ์ธ ์ ์์ต๋๋ค.
๋ธ๋ผ์ฐ์ fallback์ด ํ์ํ ๋๋ง Playwright๋ฅผ ์ค์นํฉ๋๋ค.
uv run playwright install chromium
์๋ ์์๋ ์ค์น๋
linkedin-cli๊ธฐ์ค์ ๋๋ค. clone์์ ๊ฐ๋ฐ ์ค์ด๋ฉด ๊ฐ ๋ช ๋ น ์์uv run์ ๋ถ์ด๊ฑฐ๋(uv run linkedin-cli ...),uv tool install .๋ก ์ ์ญ ์ค์นํ์ธ์.
CLI ํ์ธ:
linkedin-cli --help
์ฝ๊ธฐ ๋ช ๋ น์ LinkedIn ์น ์ธ์ ์ด ํ์ํฉ๋๋ค. ๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ ๋ก๊ทธ์ธ๋ ๋ธ๋ผ์ฐ์ ์์ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋๋ค.
linkedin-cli auth login linkedin-cli auth-status
auth login์ ๋ก๊ทธ์ธ๋ ๋ธ๋ผ์ฐ์ (ChromeยทChromiumยทBraveยทEdgeยทFirefox)์์ ์ฟ ํค๋ฅผ ์ถ์ถํด private file(~/.config/linkedin/cookies.env, ๊ถํ 600)์ ์ ์ฅํ๊ณ ์ธ์
์ ๊ฒ์ฆํฉ๋๋ค. ์๋ ์ถ์ถ์ด ์คํจํ๋ฉด DevTools๋ก ์ง์ ๋ณต์ฌํ๋ ๋จ๊ณ๋ฅผ ์ถ๋ ฅํฉ๋๋ค โ ์ฝ๊ธฐ ์ธ์ฆ ์ฐธ๊ณ . read feed๋ ์ฟ ํค๋ฅผ Python HTTP ํด๋ผ์ด์ธํธ์ ์ฌ์ฌ์ฉํ์ง ์๊ณ , ์ ์ฅ๋ Playwright browser state ์์์ GraphQL fetch๋ฅผ ์คํํฉ๋๋ค.
์๋ ์ถ์ถ์ ์ฑ๊ณตํ์ง๋ง LinkedIn Voyager๊ฐ self-redirect/authwall๋ก ์ธ์ ์ ๊ฑฐ๋ถํ๋ฉด, ์ ์น ์ธ์ ์ ์ง์ ์บก์ฒํฉ๋๋ค.
linkedin-cli auth login --via-browser --browser chrome linkedin-cli auth login --via-browser --browser firefox
Firefox๋ฅผ ์ ํํ๋ ค๋ฉด Playwright Firefox ๋น๋๊ฐ ํ์ํฉ๋๋ค: uv run playwright install firefox.
์ด ๋ช
๋ น์ Playwright ๋ธ๋ผ์ฐ์ ์ฐฝ์ ์ด๊ณ ์ฌ์ฉ์๊ฐ ์ง์ ๋ก๊ทธ์ธ/2FA/checkpoint๋ฅผ ํต๊ณผํ ๋ค ์ ์ฒด LinkedIn ์ฟ ํค jar์ browser state๋ฅผ private file์ ์ ์ฅํฉ๋๋ค. ์ฟ ํค ๊ฐ์ ์ถ๋ ฅํ์ง ์์ต๋๋ค. auth-status๋ direct HTTP ์ง๋จ์ด๋ฏ๋ก browser-context ๊ธฐ๋ฐ read feed์ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅผ ์ ์์ต๋๋ค.
๊ณต์ OAuth ๊ถํ์ mutation ์์ด ์ ๊ฒ:
linkedin-cli auth permission-check --json linkedin-cli auth permission-check --post-id urn:li:ugcPost:1234567890 --json
์ฝ๊ธฐ ๋ช ๋ น ์คํ:
linkedin-cli read feed --limit 10 --json linkedin-cli read saved --limit 10 --json linkedin-cli read profile your-handle --json linkedin-cli read profile-posts your-handle --limit 5 --json linkedin-cli read activity urn:li:activity:1234567890 --json linkedin-cli read comments urn:li:activity:1234567890 --limit 20 --json linkedin-cli read reactions urn:li:activity:1234567890 --limit 20 --json linkedin-cli read search "AI engineer" --limit 10 --json
์ฐ๊ธฐ ๋ช ๋ น์ ๊ณต์ OAuth ํ ํฐ์ด ํ์ํฉ๋๋ค.
linkedin-cli post text --text "hello from linkedin-cli" --visibility public --dry-run --json linkedin-cli post text --text "hello from linkedin-cli" --visibility public --dry-run --json --output tmp/linkedin-post-text-dry-run.json linkedin-cli post text --text "hello from linkedin-cli" --visibility public --json linkedin-cli post media --text "hello with image" --media image.png --visibility public --json linkedin-cli post multi-image --text "hello album" --media one.png --media two.jpg --dry-run --json linkedin-cli post video --text "hello video" --video clip.mp4 --title "Demo" --dry-run --json linkedin-cli post document --text "hello deck" --document deck.pdf --title "Deck" --dry-run --json linkedin-cli post poll --text "vote" --question "Pick one" --option Red --option Blue --duration three-days --dry-run --json linkedin-cli post article --text "read this" --url https://example.com/post --dry-run --json linkedin-cli post reshare urn:li:share:1234567890 --text "worth reading" --dry-run --json linkedin-cli post quote urn:li:share:1234567890 --text "worth reading" --dry-run --json linkedin-cli post reply urn:li:ugcPost:1234567890 --text "great post" --dry-run --json linkedin-cli post repost urn:li:share:1234567890 --dry-run --json linkedin-cli post update urn:li:share:1234567890 --text "updated text" --dry-run --json linkedin-cli post get urn:li:share:1234567890 --json linkedin-cli post list --limit 10 --json linkedin-cli post delete urn:li:share:1234567890 --dry-run --json linkedin-cli post delete urn:li:share:1234567890 --json linkedin-cli comment list urn:li:ugcPost:1234567890 --json linkedin-cli comment create urn:li:ugcPost:1234567890 --text "great post" --dry-run --json linkedin-cli comment create urn:li:ugcPost:1234567890 --text "great post" --dry-run --json --output tmp/linkedin-comment-create-dry-run.json linkedin-cli comment update urn:li:ugcPost:1234567890 987654321 --text "updated comment" --dry-run --json linkedin-cli comment delete urn:li:ugcPost:1234567890 987654321 --dry-run --json linkedin-cli reaction create urn:li:ugcPost:1234567890 --type like --dry-run --json linkedin-cli reaction create urn:li:ugcPost:1234567890 --type like --dry-run --json --output tmp/linkedin-reaction-create-dry-run.json linkedin-cli reaction delete urn:li:ugcPost:1234567890 --dry-run --json linkedin-cli social metadata urn:li:ugcPost:1234567890 --json linkedin-cli social metadata urn:li:ugcPost:1234567890 --json --output tmp/linkedin-social-metadata.json linkedin-cli social comments-state urn:li:ugcPost:1234567890 --state closed --dry-run --json linkedin-cli social comments-state urn:li:ugcPost:1234567890 --state closed --dry-run --json --output tmp/linkedin-comments-state-dry-run.json linkedin-cli insights media urn:li:ugcPost:1234567890 --json linkedin-cli insights organization urn:li:organization:123456 --json linkedin-cli insights user --json linkedin-cli insights user --json --output tmp/linkedin-insights-user.json
๊ธด ๊ธ์ด๋ ์์ฑ๋ ๊ธ์ inline text๋ณด๋ค ํ์ผ ์ ๋ ฅ์ ๊ถ์ฅํฉ๋๋ค.
linkedin-cli post text --text-file draft.md --visibility public --dry-run --json linkedin-cli post text --text-file draft.md --visibility public --json
๊ณต์ post.* ๋ช
๋ น์ LinkedIn Developer app๊ณผ w_member_social ๊ถํ์ด ์๋ access token์ด ํ์ํฉ๋๋ค.
๊ณต์ comment.*, reaction.*, social.* ๋ช
๋ น์ LinkedIn app/product ์น์ธ ์ํ์ ๋ฐ๋ผ w_member_social_feed, r_member_social_feed, w_organization_social_feed, r_organization_social_feed ๊ฐ์ ์ถ๊ฐ ๊ถํ์ด ํ์ํ ์ ์์ต๋๋ค.
LinkedIn Developer Portal์ ์ฝ๋๋ค.
https://www.linkedin.com/developers/apps
์ฑ์ ๋ง๋ค๊ณ ํ์ ํญ๋ชฉ์ ์ฑ์๋๋ค.
- App name
- LinkedIn Page
- Privacy policy URL
- App logo
- API Terms ๋์
LinkedIn Page๊ฐ ์๋ค๋ฉด ์๋ก ๋ง๋ค๊ฑฐ๋, ๊ฐ์ธ ๊ฐ๋ฐ์์๊ฒ ํ์ฉ๋๋ ๊ธฐ๋ณธ Page๋ฅผ ์ ํํฉ๋๋ค.
์ฑ์ Products/Auth ์ค์ ์์ ์๋ scope๋ฅผ ์์ฒญํ ์ ์์ด์ผ ํฉ๋๋ค.
openidprofileemailw_member_social
CLI๋ openid profile email๋ก ์ธ์ฆ๋ ๋ฉค๋ฒ๋ฅผ ์๋ณํ๊ณ , w_member_social๋ก ํด๋น ๋ฉค๋ฒ์ ๊ฒ์๊ธ ์์ฑ/์์ /์ญ์ ๋ฅผ ์ํํฉ๋๋ค.
๋๊ธ/๋ฐ์/์์
๋ฉํ๋ฐ์ดํฐ ๋ช
๋ น์ LinkedIn์ Social Feed ๊ถํ์ด ์์ด์ผ ์ฑ๊ณตํฉ๋๋ค. ๊ถํ์ด ์์ผ๋ฉด CLI๋ permission_denied JSON envelope๋ฅผ ๋ฐํํฉ๋๋ค.
์ฑ์ Auth ํญ์ CLI๊ฐ ์ฌ์ฉํ๋ ๋ก์ปฌ callback URL์ ์ถ๊ฐํฉ๋๋ค.
http://localhost:8787/callback
host override๋ฅผ ์ฐ๋ ค๋ฉด ์๋ URL๋ ์ถ๊ฐํ ์ ์์ต๋๋ค.
http://127.0.0.1:8787/callback
redirect URI๋ ์ ํํ ์ผ์นํด์ผ ํฉ๋๋ค. --redirect-uri๋ LINKEDIN_REDIRECT_URI๋ฅผ ์ด๋ค๋ฉด ๊ทธ ๊ฐ์ Developer Portal์๋ ๊ทธ๋๋ก ์ถ๊ฐํด์ผ ํฉ๋๋ค.
Auth ํญ์์ ์ฑ credential์ ๋ณต์ฌํฉ๋๋ค.
ํ๊ฒฝ ๋ณ์ ๋ฐฉ์:
export LINKEDIN_CLIENT_ID='...' export LINKEDIN_CLIENT_SECRET='...'
๋ก์ปฌ OAuth flow๋ฅผ ์คํํฉ๋๋ค.
linkedin-cli auth oauth-login
์ ์ฉํ ์ต์ :
linkedin-cli auth oauth-login --json --output tmp/linkedin-auth-oauth-login.json linkedin-cli auth oauth-login --timeout 300 linkedin-cli auth oauth-login --no-open linkedin-cli auth oauth-login --redirect-uri http://localhost:8787/callback
์ด ๋ช
๋ น์ LinkedIn OAuth๋ฅผ ๋ธ๋ผ์ฐ์ ์์ ์ด๊ณ , callback state๋ฅผ ๊ฒ์ฆํ๊ณ , ์ธ์ฆ๋ ๋ฉค๋ฒ๋ฅผ ์กฐํํ ๋ค ์๋ ํ์ผ์ ํ ํฐ์ ์ ์ฅํฉ๋๋ค.
~/.config/linkedin/oauth.json
ํ ํฐ ํ์ผ ๊ตฌ์กฐ:
{
"access_token": "...",
"author_urn": "urn:li:person:...",
"linkedin_version": "202605"
}์ด ํ์ผ์ ๋น๊ณต๊ฐ๋ก ๋ณด๊ดํด์ผ ํฉ๋๋ค. CLI๋ ์ฌ์ฉ์ ๋ณธ์ธ๋ง ์ฝ์ ์ ์๋ ํ์ผ๋ก ์ทจ๊ธํฉ๋๋ค.
ํญ์ dry-run์ ๋จผ์ ์คํํฉ๋๋ค.
linkedin-cli post text --text "token smoke test" --visibility public --dry-run --json์ต์ข ๋ฌธ๊ตฌ๊ฐ ํ์ ๋ ๋ค์๋ง ๊ฒ์ํฉ๋๋ค.
linkedin-cli post text --text-file draft.md --visibility public --json
๋ฐํ๋ post id๋ก ์ญ์ ํ ์ ์์ต๋๋ค.
linkedin-cli post delete urn:li:share:1234567890 --dry-run --json linkedin-cli post delete urn:li:share:1234567890 --json
Oops. We can't verify the authenticity of your request because the state parameter was modified.
linkedin-cli auth oauth-login์ ์๋ก ์คํํฉ๋๋ค.- ์ค๋๋ OAuth URL์ ์ฌ์ฌ์ฉํ์ง ์์ต๋๋ค.
- CLI๊ฐ ์ฐ ๋ธ๋ผ์ฐ์ ํญ์์ flow๋ฅผ ์๋ฃํฉ๋๋ค.
- Developer Portal์ redirect URI๊ฐ CLI redirect URI์ ์ ํํ ๊ฐ์์ง ํ์ธํฉ๋๋ค.
- ์ค๋๋ localhost callback ํ์ด์ง๊ฐ ์ด๋ ค ์์ผ๋ฉด ๋ซ๊ณ ๋ค์ ์๋ํฉ๋๋ค.
permission_denied ๋๋ w_member_social ๋๋ฝ
- ์ฑ์ Share on LinkedIn / member social product๊ฐ ํ์ฑํ๋์ด ์๋์ง ํ์ธํฉ๋๋ค.
- product/scope๋ฅผ ํ์ฑํํ ๋ค
auth oauth-login์ ๋ค์ ์คํํฉ๋๋ค. - OAuth ๋์ ํ๋ฉด์
w_member_social์ด ํ์๋๋์ง ํ์ธํฉ๋๋ค. - ๋๊ธ/๋ฐ์/์์
๋ฉํ๋ฐ์ดํฐ ๋ช
๋ น์ด๋ฉด
w_member_social_feed/r_member_social_feed๋๋ organization social feed ๊ถํ์ด ํ์ํ์ง ํ์ธํฉ๋๋ค.
auth_expired
linkedin-cli auth oauth-login์ ๋ค์ ์คํํฉ๋๋ค.
๊ณต์ ์ฐธ๊ณ ๋ฌธ์:
- LinkedIn OAuth 2.0 Authorization Code Flow: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow
- Share on LinkedIn: https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin
- LinkedIn Posts API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api
- LinkedIn MultiImage Post API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/multiimage-post-api
- LinkedIn Videos API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/videos-api
- LinkedIn Documents API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/documents-api
- LinkedIn Poll API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/poll-post-api
- LinkedIn Comments API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/comments-api
- LinkedIn Reactions API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/reactions-api
- LinkedIn Social Metadata API: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/social-metadata-api
์ฝ๊ธฐ ์ธ์ฆ์ ๊ณต์ ์ฐ๊ธฐ OAuth์ ๋ถ๋ฆฌ๋์ด ์์ต๋๋ค.
ํด๊ฒฐ ์์:
LINKEDIN_COOKIE_HEADERLINKEDIN_LI_AT+LINKEDIN_JSESSIONIDLINKEDIN_COOKIE_FILE๋๋ ๊ธฐ๋ณธ ํ์ผ~/.config/linkedin/cookies.env- Chrome, Chromium, Brave, Edge, Firefox์ ๋ธ๋ผ์ฐ์ cookie ์ถ์ถ
๊ถ์ฅ: auth login์ผ๋ก ์๋ ์บก์ฒ. ๋ก๊ทธ์ธ๋ ๋ธ๋ผ์ฐ์ ์์ ์ฟ ํค๋ฅผ ์ถ์ถํด private file(๊ถํ 600)์ ์ ์ฅํ๊ณ ์ธ์
์ ๊ฒ์ฆํฉ๋๋ค. ๊ฐ์ ์ ๋ ์ถ๋ ฅํ์ง ์์ต๋๋ค.
linkedin-cli auth login linkedin-cli auth-status
์๋ ์ถ์ถ๋ ์ฟ ํค๊ฐ LinkedIn Voyager์์ ๊ฑฐ๋ถ๋๋ฉด Playwright ๋ธ๋ผ์ฐ์ ์ฐฝ์ผ๋ก ์ ์ธ์ ์ ์ง์ ์บก์ฒํฉ๋๋ค.
linkedin-cli auth login --via-browser --browser chrome linkedin-cli auth login --via-browser --browser firefox
Firefox๋ฅผ ์ ํํ๋ ค๋ฉด Playwright Firefox ๋น๋๊ฐ ํ์ํฉ๋๋ค: uv run playwright install firefox.
์๋ ์ถ์ถ์ด ์คํจํ๋ฉด(macOS๋ ChromeยทBraveยทEdge๊ฐ Keychain ์ ๊ทผ์ ์๊ตฌ โ --browser firefox๊ฐ ๊ฐ์ฅ ์์ ์ ) ์๋ ๋จ๊ณ๋ก ์ง์ ์บก์ฒํฉ๋๋ค.
์๋ ์บก์ฒ (DevTools):
- ๋ธ๋ผ์ฐ์ ์์ https://www.linkedin.com ์ ๋ก๊ทธ์ธ๋ ์ํ๋ฅผ ํ์ธํฉ๋๋ค.
- DevTools๋ฅผ ์ฝ๋๋ค (macOS
Option+Command+I, ๋๋F12). - Application ํญ โ Storage โ Cookies โ
https://www.linkedin.com. li_at์JSESSIONID๊ฐ์ ๋ณต์ฌํฉ๋๋ค (JSESSIONID๋"ajax:..."ํํ์ด๋ ๋ฐ์ดํ๋ฅผ ํฌํจํด ๋ณต์ฌ).- ํ ์ค๋ก ๋ง๋ญ๋๋ค:
li_at=<๊ฐ>; JSESSIONID=<๊ฐ> - ์ ์ฅ:
linkedin-cli auth cookie-file --from-stdin์ ์คํํ๊ณ ๊ทธ ์ค์ ๋ถ์ฌ๋ฃ์ ๋คReturn,Ctrl-D. - ๊ฒ์ฆ:
linkedin-cli auth-status
๋๋ DevTools Network ํญ์์ www.linkedin.com ์์ฒญ์ cookie: request header ์ ์ฒด๋ฅผ ๋ณต์ฌํด ๊ฐ์ ๋ช
๋ น์ ๋ถ์ฌ๋ฃ์ด๋ ๋ฉ๋๋ค(LinkedIn์ด ๊ฐ๋ ์๊ตฌํ๋ ๋ ์์ ํ cookie jar).
์ด ๊ฐ๋ค์ LinkedIn ๋น๋ฐ๋ฒํธ์ ๊ฐ์ต๋๋ค โ ์ฑํ ์ ๋ถ์ฌ๋ฃ๊ฑฐ๋ ์ปค๋ฐยท๊ณต์ ํ์ง ๋ง์ธ์.
์ผํ์ฑ env ๋ฐฉ์:
export LINKEDIN_COOKIE_HEADER='li_at=...; JSESSIONID="ajax:..."; ...' linkedin-cli auth-status
์ต์ cookie ๋ณ์. authwall/checkpoint๋ redirect๊ฐ ๋์ค๋ฉด ์ ์ฒด Cookie header๋ฅผ ์ฌ์ฉํ์ธ์.
export LINKEDIN_LI_AT='AQ...' export LINKEDIN_JSESSIONID='"ajax:123456789"'
์ ํ์ ๋ธ๋ผ์ฐ์ ์ค์ :
export LINKEDIN_BROWSER='chrome' export LINKEDIN_HEADLESS='1' export LINKEDIN_PROXY='http://127.0.0.1:7890' export LINKEDIN_CONFIG="$PWD/config.yaml" export LINKEDIN_COOKIE_FILE="$HOME/.config/linkedin/cookies.env" export LINKEDIN_BROWSER_STATE="$HOME/.config/linkedin-cli/browser-state.json"
ํ์ค JSON ๋ช ๋ น:
linkedin-cli auth-status linkedin-cli auth oauth-login linkedin-cli read feed --limit 20 --json linkedin-cli read saved --limit 20 --json linkedin-cli read profile your-handle --json linkedin-cli read profile-posts your-handle --limit 5 --json linkedin-cli read activity urn:li:activity:1234567890 --json linkedin-cli read comments urn:li:activity:1234567890 --limit 20 --json linkedin-cli read reactions urn:li:activity:1234567890 --limit 20 --json linkedin-cli read search "product manager" --limit 10 --json linkedin-cli saved list --limit 20 --json linkedin-cli saved unsave urn:li:activity:123 --dry-run --json linkedin-cli post text --text "hello" --visibility public --dry-run --json linkedin-cli post text --text-file draft.md --visibility public --json linkedin-cli post media --text "hello with image" --media image.png --visibility public --json linkedin-cli post multi-image --text "hello album" --media one.png --media two.jpg --json linkedin-cli post video --text "hello video" --video clip.mp4 --title "Demo" --json linkedin-cli post document --text "hello deck" --document deck.pdf --title "Deck" --json linkedin-cli post poll --text "vote" --question "Pick one" --option Red --option Blue --duration three-days --json linkedin-cli post article --text "read this" --url https://example.com/post --json linkedin-cli post reshare urn:li:share:1234567890 --text "worth reading" --json linkedin-cli post quote urn:li:share:1234567890 --text "worth reading" --json linkedin-cli post reply urn:li:ugcPost:1234567890 --text "great post" --json linkedin-cli post repost urn:li:share:1234567890 --dry-run --json linkedin-cli post update urn:li:share:1234567890 --text "updated text" --json linkedin-cli post get urn:li:share:1234567890 --json linkedin-cli post list --limit 10 --json linkedin-cli post delete urn:li:share:1234567890 --dry-run --json linkedin-cli post delete urn:li:share:1234567890 --json linkedin-cli comment list urn:li:ugcPost:1234567890 --json linkedin-cli comment get urn:li:ugcPost:1234567890 987654321 --json linkedin-cli comment create urn:li:ugcPost:1234567890 --text "great post" --dry-run --json linkedin-cli comment update urn:li:ugcPost:1234567890 987654321 --text "updated comment" --dry-run --json linkedin-cli comment delete urn:li:ugcPost:1234567890 987654321 --dry-run --json linkedin-cli reaction list urn:li:ugcPost:1234567890 --json linkedin-cli reaction get urn:li:ugcPost:1234567890 --json linkedin-cli reaction create urn:li:ugcPost:1234567890 --type like --dry-run --json linkedin-cli reaction delete urn:li:ugcPost:1234567890 --dry-run --json linkedin-cli social metadata urn:li:ugcPost:1234567890 --json linkedin-cli social metadata urn:li:ugcPost:1234567890 --json --output tmp/linkedin-social-metadata.json linkedin-cli social comments-state urn:li:ugcPost:1234567890 --state open --dry-run --json linkedin-cli insights media urn:li:ugcPost:1234567890 --json linkedin-cli insights organization urn:li:organization:123456 --json linkedin-cli insights user --json linkedin-cli insights user --json --output tmp/linkedin-insights-user.json
Legacy ํธํ ๋ช ๋ น:
linkedin-cli feed --max 10 linkedin-cli search "product manager" --max 10 linkedin-cli profile your-handle --json --output tmp/linkedin-profile.json linkedin-cli profile-posts your-handle --max 20 linkedin-cli activity urn:li:activity:123 --json --output tmp/linkedin-activity.json linkedin-cli post "hello from browser fallback" linkedin-cli react urn:li:activity:123 --type like linkedin-cli unreact urn:li:activity:123 linkedin-cli save urn:li:activity:123 linkedin-cli unsave urn:li:activity:123 linkedin-cli comment urn:li:activity:123 "nice post"
๋ชจ๋ ํ์ค --json ๋ช
๋ น์ ํ๋์ sns-json-v1 envelope๋ง ์ถ๋ ฅํฉ๋๋ค.
{
"schema_version": "sns-json-v1",
"ok": true,
"platform": "linkedin",
"command": "post.text",
"source": "official",
"request": {},
"data": {},
"error": null,
"warnings": [],
"meta": {
"cli_name": "linkedin-cli"
}
}secret์ request, data, raw, log์ ์ฐ์ง ์์ต๋๋ค.
from pathlib import Path from linkedin_cli import LinkedInWriteAPI api = LinkedInWriteAPI.from_config() plan = api.plan_text_post(text=Path("draft.md").read_text(), visibility="public") print(plan.to_dict()) result = api.create_text_post(text=Path("draft.md").read_text(), visibility="public") print(result.url) delete_plan = api.plan_delete_post(post_id=result.post_id) print(delete_plan.to_dict()) delete_result = api.delete_post(post_id=result.post_id) print(delete_result.deleted_at)
์ด repo๋ ์
์
, ์ธ์ฆ, ์ฝ๊ธฐ/์ฐ๊ธฐ ์ํฌํ๋ก, ๋ช
๋ น ์ ํ์ ํ๋๋ก ๋ค๋ฃจ๋ project-local linkedin-cli skill์ ํฌํจํฉ๋๋ค. ์ ๋ณธ์ .agents/skills/linkedin-cli/SKILL.md์ด๋ฉฐ, skills/, .claude/skills/, .codex/skills/๋ ์ด skill๋ก ์ฐ๊ฒฐ๋ project-local ์ฌ๋ณผ๋ฆญ ๋งํฌ์
๋๋ค. ํ๋ฌ๊ทธ์ธ์ผ๋ก๋ ๊ฐ์ skill์ ์ค์นํ ์ ์์ต๋๋ค(.claude-plugin/plugin.json). ํ๋ฌ๊ทธ์ธ์ skill๋ง ์ ๊ณตํ๋ฏ๋ก, skill์ ์ฒ์ ์ธ ๋ linkedin-cli ๋ช
๋ น์ด ์์ผ๋ฉด scripts/ensure-cli.sh๊ฐ agent-linkedin์ ์๋ ์ค์นํฉ๋๋ค(uv ๋๋ pipx๊ฐ ๋จผ์ ์ค์น๋ผ ์์ด์ผ ํฉ๋๋ค). ๊ทธ ๋ค auth login โ auth-status๋ก read ์ธ์ฆ์ ํ์ธํฉ๋๋ค.
SKILL.mdโ skill entrypoint- initial-setup.md โ ์ฒซ ์ ์ ๊ณผ OAuth/์ฟ ํค ์ธ์ฆ
- command-cookbook.md โ ์ ํํ ๋ช ๋ น ํจํด๊ณผ JSON ์ฌ์ฉ
- auth-troubleshooting.md โ ์ธ์ ๋ณต๊ตฌ์ ์ง๋จ
- write-workflows.md โ ๊ณต์ ๋ฐํ๊ณผ ์์ ํ mutation
uv sync --extra dev
uv run playwright install chromium
uv run ruff check .
uv run pytest -q
uv run python -m compileall linkedin_cli testsํ ์คํธ ๊ท์น:
- Unit test๋ live LinkedIn session์ ์์กดํ์ง ์์์ผ ํฉ๋๋ค.
- ๋คํธ์ํฌ์ ๋ฏผ๊ฐํ ๋์์ transport/browser abstraction ๋ค์ ๋ก๋๋ค.
- ๋ฆด๋ฆฌ์ค ์ live verification์ ์ ์ฉํ์ง๋ง ์ผ๋ฐ CI์ ํ์ ์กฐ๊ฑด์ผ๋ก ๋์ง ์์ต๋๋ค.
- cookie, OAuth token, HAR file, browser storage state๋ฅผ commitํ์ง ์์ต๋๋ค.
LINKEDIN_COOKIE_HEADER,li_at,JSESSIONID,~/.config/linkedin/cookies.env, access token, client secret, token file์ issue๋ PR์ ๋ถ์ด์ง ์์ต๋๋ค.- screenshot, log, terminal transcript๋ฅผ ๊ณต์ ํ๊ธฐ ์ ์ secret์ ์ ๊ฑฐํฉ๋๋ค.
SECURITY.md๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์๋ ๋ฌธ์๋ฅผ ๋จผ์ ์ฝ์ด์ฃผ์ธ์.
MIT. LICENSE๋ฅผ ์ฐธ๊ณ ํ์ธ์.
linkedin-cli๋ Juan Francisco Lebrero์ frizynn/linkedin-cli์์ ์์ํ์ต๋๋ค. ์ด fork๋ ๊ณต์ LinkedIn OAuth publishing, JSON contract layer, Python write API, Codex/Claude skill packaging์ ์ถ๊ฐํฉ๋๋ค. ์๋ณธ ์์
์ MIT ๋ผ์ด์ ์ค์ด๋ฉฐ copyright๋ LICENSE์ ๋ณด์กด๋์ด ์์ต๋๋ค.