Skip to content

ai-native-engineer/linkedin-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

25 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

linkedin-cli

AI ์—์ด์ „ํŠธ๊ฐ€ LinkedIn์„ ๋‹ค๋ฃจ๋Š” CLI โ€” ๋น„๊ณต์‹ ์ฝ๊ธฐ์™€ ๊ณต์‹ OAuth ๊ฒŒ์‹œ๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ

PyPI CI license python

ํ•œ๊ตญ์–ด ยท 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-v1 JSON ์ถœ๋ ฅ

์“ฐ๊ธฐ:

  • ์‹ค์ œ ๋ฐœํ–‰ ์ „ ๊ณต์‹ ๊ฒŒ์‹œ 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-linkedin

agent-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

๊ณต์‹ OAuth ํ† ํฐ ๋ฐœ๊ธ‰

๊ณต์‹ 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 ๊ฐ™์€ ์ถ”๊ฐ€ ๊ถŒํ•œ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. LinkedIn Developer app ๋งŒ๋“ค๊ธฐ

LinkedIn Developer Portal์„ ์—ฝ๋‹ˆ๋‹ค.

https://www.linkedin.com/developers/apps

์•ฑ์„ ๋งŒ๋“ค๊ณ  ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ฑ„์›๋‹ˆ๋‹ค.

  • App name
  • LinkedIn Page
  • Privacy policy URL
  • App logo
  • API Terms ๋™์˜

LinkedIn Page๊ฐ€ ์—†๋‹ค๋ฉด ์ƒˆ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, ๊ฐœ์ธ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ํ—ˆ์šฉ๋˜๋Š” ๊ธฐ๋ณธ Page๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

2. ํ•„์š”ํ•œ product์™€ scope ํ™œ์„ฑํ™”

์•ฑ์˜ Products/Auth ์„ค์ •์—์„œ ์•„๋ž˜ scope๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • openid
  • profile
  • email
  • w_member_social

CLI๋Š” openid profile email๋กœ ์ธ์ฆ๋œ ๋ฉค๋ฒ„๋ฅผ ์‹๋ณ„ํ•˜๊ณ , w_member_social๋กœ ํ•ด๋‹น ๋ฉค๋ฒ„์˜ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋Œ“๊ธ€/๋ฐ˜์‘/์†Œ์…œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ช…๋ น์€ LinkedIn์˜ Social Feed ๊ถŒํ•œ์ด ์žˆ์–ด์•ผ ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ถŒํ•œ์ด ์—†์œผ๋ฉด CLI๋Š” permission_denied JSON envelope๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

3. Redirect URL ์ถ”๊ฐ€

์•ฑ์˜ 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์—๋„ ๊ทธ๋Œ€๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. Client ID์™€ Client Secret ์„ค์ •

Auth ํƒญ์—์„œ ์•ฑ credential์„ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ฐฉ์‹:

export LINKEDIN_CLIENT_ID='...'
export LINKEDIN_CLIENT_SECRET='...'

5. ํ† ํฐ ๋ฐœ๊ธ‰ ๋ฐ ์ €์žฅ

๋กœ์ปฌ 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๋Š” ์‚ฌ์šฉ์ž ๋ณธ์ธ๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ํŒŒ์ผ๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.

6. ๊ฒŒ์‹œ ์ „ ๊ฒ€์ฆ

ํ•ญ์ƒ 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

OAuth ๋ฌธ์ œ ํ•ด๊ฒฐ

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์„ ๋‹ค์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ณต์‹ ์ฐธ๊ณ  ๋ฌธ์„œ:

์ฝ๊ธฐ ์ธ์ฆ

์ฝ๊ธฐ ์ธ์ฆ์€ ๊ณต์‹ ์“ฐ๊ธฐ OAuth์™€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ์ˆœ์„œ:

  1. LINKEDIN_COOKIE_HEADER
  2. LINKEDIN_LI_AT + LINKEDIN_JSESSIONID
  3. LINKEDIN_COOKIE_FILE ๋˜๋Š” ๊ธฐ๋ณธ ํŒŒ์ผ ~/.config/linkedin/cookies.env
  4. 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):

  1. ๋ธŒ๋ผ์šฐ์ €์—์„œ https://www.linkedin.com ์— ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  2. DevTools๋ฅผ ์—ฝ๋‹ˆ๋‹ค (macOS Option+Command+I, ๋˜๋Š” F12).
  3. Application ํƒญ โ†’ Storage โ†’ Cookies โ†’ https://www.linkedin.com.
  4. li_at์™€ JSESSIONID ๊ฐ’์„ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค (JSESSIONID๋Š” "ajax:..." ํ˜•ํƒœ์ด๋‹ˆ ๋”ฐ์˜ดํ‘œ๋ฅผ ํฌํ•จํ•ด ๋ณต์‚ฌ).
  5. ํ•œ ์ค„๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค: li_at=<๊ฐ’>; JSESSIONID=<๊ฐ’>
  6. ์ €์žฅ: linkedin-cli auth cookie-file --from-stdin์„ ์‹คํ–‰ํ•˜๊ณ  ๊ทธ ์ค„์„ ๋ถ™์—ฌ๋„ฃ์€ ๋’ค Return, Ctrl-D.
  7. ๊ฒ€์ฆ: 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 ๊ณ„์•ฝ

๋ชจ๋“  ํ‘œ์ค€ --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์— ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Python API

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)

Skills

์ด 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์— ๋ณด์กด๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

About

Terminal-first LinkedIn CLI: unofficial session-based reads + official OAuth publishing, with packaged Codex/Claude skills

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors