ilml-plugin-linkedin 1.20.10 → 1.20.11

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.11
4
+ - **Fixed: `read-post` reported "Auth wall — cookies expired or not logged in" on a post you could actually open — even while every other command was signed in on the same account.** Opening a post by its direct link lands on LinkedIn's brief "signing you back in" screen more often than an in-app click does, and `read-post` decided too early that you'd been logged out, with no attempt to recover. It now waits through that screen and self-heals the same way the other commands do (reopens your feed to refresh the session, then retries the post), so a valid session reads the post instead of failing. It only reports a real sign-in problem if the wall is still up after recovery.
5
+ - **Fixed (Windows): a `read-post` that hit that sign-in wall could leave a hidden browser open that blocked your next command.** On the way out it force-closed its browser in a way that doesn't work on Windows, so the browser stayed running and held the automation profile — and the next command failed with "another LinkedIn automation command is already running". `read-post` now closes its browser cleanly before it exits.
6
+ - **Fixed: `scout` aborted the whole run with "session expired" the moment LinkedIn showed its sign-in wall on a job search — even when your session was still good.** Job-search links trigger that wall more readily than other pages, and `scout` gave up instantly instead of recovering. It now self-heals the same way the rest of the plugin does (refresh via your feed, then return to the search) before concluding you're signed out, and only stops if it genuinely can't get back in.
7
+ - No schema change.
8
+
3
9
  ## 1.20.10
4
10
  - **Fixed: `messages --push-drafts` could report a message as "sent" without actually delivering it.** When the draft's thread was already open in your inbox, the send ran on the shared messaging page and could land on the WRONG message box — a leftover floating chat bubble for a different person — then see that box clear and report "✓ sent" while the intended person received nothing. Push now prefers the reliable path that opens the recipient's own profile and confirms the recipient before sending (the same path `--send` has used since 1.17.2, which the draft push never actually adopted), and only falls back to the inbox path when no profile link is on file. A send that can't be confirmed is now reported as "failed" (your draft is kept) instead of a false "sent". Under the hood, the "did it actually send?" check is now one shared piece of logic used by both send paths, so it can't drift out of sync between them again.
5
11
  - **Fixed: `messages --send "First Last" "text"` no longer sends a garbled fragment when the name or message contains spaces.** A quoted multi-word argument can get split on spaces before the plugin sees it, which could turn `--send "Jane Doe" "hi there"` into sending just a stray word. `--send` now detects the split and refuses with a clear message pointing you to the file-based form (`--send-batch @file.json`, or `--draft-batch @file.json` → `--push-drafts`), instead of delivering garbage.
package/dist/readPost.mjs CHANGED
@@ -1,16 +1,18 @@
1
- var Pe=Object.defineProperty;var ve=(e,o)=>()=>(e&&(o=e(e=0)),o);var Ae=(e,o)=>{for(var t in o)Pe(e,t,{get:o[t],enumerable:!0})};var we={};Ae(we,{graph:()=>Ne});import{createGraphClient as Re,resolveToken as Me}from"@ilivemylife/graph-sdk";import{config as Fe}from"dotenv";var ge,Ne,ye=ve(()=>{Fe();ge=Me()||process.env.TOKEN;ge||(console.error(`
1
+ var Ie=Object.defineProperty;var _e=(e,o)=>()=>(e&&(o=e(e=0)),o);var Te=(e,o)=>{for(var t in o)Ie(e,t,{get:o[t],enumerable:!0})};var xe={};Te(xe,{graph:()=>Ge});import{createGraphClient as He,resolveToken as Be}from"@ilivemylife/graph-sdk";import{config as We}from"dotenv";var Se,Ge,ke=_e(()=>{We();Se=Be()||process.env.TOKEN;Se||(console.error(`
2
2
  [FATAL] No iLiveMyLife token found.`),console.error(" The bot uses Lifebot AI to decide which jobs to apply for"),console.error(` and how to fill application forms. It cannot work without it.
3
3
  `),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
4
4
  `),console.error(` Or add ILML_TOKEN=... to .env
5
- `),process.exit(1));Ne=Re({token:ge})});import"dotenv/config";import Ue from"puppeteer";import x from"fs";import A from"path";var _e="cookies.json";function Z(){let e=process.env.ILML_SCOPE_DIR;return e?A.join(e,"plugins-state","linkedin","cookies.json"):_e}function z(){let e=Z();if(!x.existsSync(e))throw new Error(`Cookies not found at ${e}. Run: ilml linkedin login`);let o;try{o=x.readFileSync(e,"utf-8")}catch(a){throw new Error(`Failed to read cookies at ${e}: ${a.message}. Run: ilml linkedin login`)}let t;try{t=JSON.parse(o)}catch{throw new Error(`Cookies file at ${e} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(t)||t.length===0)throw new Error(`Cookies file at ${e} is empty or not an array. Run: ilml linkedin login`);return t}function V(){return x.existsSync(Z())}function Ie(){let e=process.env.ILML_SCOPE_DIR;return e?A.join(e,"plugins-state","linkedin"):"."}function Q(e){let o=String(e||"").replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,80);return/[a-zA-Z0-9]/.test(o)?o:"default"}function Te(){let e=process.env.MY_LINKEDIN_URL,o=e&&e.match(/\/in\/([^/?#]+)/);return o&&o[1]?Q(o[1]):"default"}function ee(e){let o=process.env.LINKEDIN_CHROME_PROFILE_DIR;if(o)return x.existsSync(o)||x.mkdirSync(o,{recursive:!0}),A.resolve(o);let t=Q(e||Te()),a=A.join(Ie(),"chrome-profile",t);return x.existsSync(a)||x.mkdirSync(a,{recursive:!0}),A.resolve(a)}var te=".ilml-seeded";function oe(e){return!x.existsSync(A.join(e,te))}function ne(e){try{x.writeFileSync(A.join(e,te),new Date().toISOString())}catch{}}function se(e){let o=0;for(let t of["SingletonLock","SingletonCookie","SingletonSocket"])try{x.rmSync(A.join(e,t),{force:!0}),o++}catch{}return o}async function ie(e,o,{timeout:t=45e3,poll:a=1e3,label:p="page"}={}){let i=Date.now();for(;Date.now()-i<t;){try{if(await e.evaluate(g=>!!document.querySelector(g),o)){let g=Date.now()-i;return g>6e3&&console.log(` (waited ${Math.round(g/1e3)}s for LinkedIn to finish loading ${p})`),!0}}catch{}await new Promise(m=>setTimeout(m,a))}return!1}var B=e=>new Promise(o=>setTimeout(o,e));function re(e){return e?e.split("?")[0].split("#")[0]:""}async function ae(e,o,t={}){let a=t.commentPasses??4,p=t.skipComments??!1,i=Number.isFinite(t.commentsMax)?t.commentsMax:1/0;if(await e.goto(o,{waitUntil:"domcontentloaded",timeout:6e4}),await ie(e,'main, .scaffold-layout__main, input[name="session_key"]',{timeout:45e3,label:"post"}),await B(1e3),await e.evaluate(()=>/\/login|\/authwall|\/checkpoint|\/uas\//.test(location.pathname)||!!document.querySelector('input[name="session_key"]')))return{authWall:!0};let g=p?2:6;for(let s=0;s<g;s++)await e.evaluate(()=>window.scrollBy(0,1200)),await B(p?500:1e3);if(p)await e.evaluate(()=>{for(let s of document.querySelectorAll("button")){let f=`${s.innerText||""} ${s.getAttribute("aria-label")||""}`;if(/^(see more|…more|show more)$/i.test(f.trim()))try{s.click()}catch{}}}),await B(500);else for(let s=0;s<a;s++){let f=await e.evaluate(()=>{let c=0;for(let u of document.querySelectorAll("button")){let y=`${u.innerText||""} ${u.getAttribute("aria-label")||""}`;if(/see more|…more|show more|load more comments|more comments|previous replies|more replies|показать ещё|ещё|загрузить ещё|показать предыдущие/i.test(y))try{u.click(),c++}catch{}}return c});if(await B(1800),!f)break}let n=await e.evaluate(()=>{let s=(h,w)=>{for(let v of w){let k=h&&h.querySelector(v);if(k&&(k.innerText||"").trim())return k.innerText.trim()}return""},f=(h,w)=>{for(let v of w){let k=h&&h.querySelector(v);if(k&&k.href)return k.href}return""},c=document.querySelector("main")||document.body,u=document.querySelector(".fie-impression-container")||document.querySelector(".feed-shared-update-v2")||c,y={name:s(u,['.update-components-actor__title span[aria-hidden="true"]','.update-components-actor__name span[aria-hidden="true"]',".update-components-actor__title",".update-components-actor__name"]),profileUrl:f(u,['.update-components-actor__container a[href*="/in/"]','.update-components-actor__meta a[href*="/in/"]','a.app-aware-link[href*="/in/"]']),headline:s(u,[".update-components-actor__description"])},E=s(u,[".update-components-update-v2__commentary",".feed-shared-update-v2__description",".update-components-text"]),M=s(u,['.update-components-actor__sub-description span[aria-hidden="true"]',".update-components-actor__sub-description"]),F=s(document,[".social-details-social-counts__reactions-count",".social-details-social-counts__social-proof-fallback-number"]),N=s(document,['button[aria-label*="comment" i] span',".social-details-social-counts__comments"]),P=s(document,['button[aria-label*="repost" i] span',".social-details-social-counts__reposts",".social-details-social-counts__shares"]),d=u.querySelector(".update-components-update-v2__commentary")||u.querySelector(".update-components-text")||u,$=Array.from(d?.querySelectorAll?.('a[href*="/in/"], a[href*="/company/"]')||[]),T=[],U=new Set;for(let h of $){let w=(h.href||"").split("?")[0].replace(/\/$/,"");if(!w||U.has(w))continue;U.add(w);let v=(h.textContent||"").trim(),k=w.includes("/company/")?"company":"person";T.push({kind:k,url:w,label:v||null})}let j=Array.from(new Set(((E||"")+" "+(d?.textContent||"")).match(/#[\p{L}\p{N}_]{2,40}/gu)||[])),J={hasImage:!!u.querySelector(".update-components-image, img.feed-shared-image__image"),hasVideo:!!u.querySelector("video, .update-components-video, .feed-shared-update-v2__content video"),hasArticle:!!u.querySelector(".update-components-article, .feed-shared-article"),hasDocument:!!u.querySelector(".feed-shared-document, .update-components-document"),hasPoll:!!u.querySelector(".update-components-poll, .feed-shared-poll")},q=document.querySelectorAll("article.comments-comment-entity, .comments-comment-entity, .comments-comment-item"),l=[];for(let h of q){let w=s(h,[".comments-comment-meta__description-title",'.comments-post-meta__name-text span[aria-hidden="true"]',".comments-post-meta__name-text"]),v=s(h,[".comments-comment-item__main-content",".comments-comment-entity__content .update-components-text",".comments-comment-entity__content",".update-components-text"]);!w&&!v||l.push({author:{name:w,profileUrl:f(h,['a[href*="/in/"]']),headline:s(h,[".comments-comment-meta__description-subtitle",".comments-post-meta__headline"])},text:v})}let H=new Set,Ce=l.filter(h=>{let w=`${h.author.name}|${h.text.slice(0,80)}`;return H.has(w)?!1:(H.add(w),!0)});return{url:location.href,author:y,text:E,postedAt:M,reactions:F,commentCount:N,repostCount:P,hashtags:j,mentions:T,media:J,comments:Ce,fullText:(c.innerText||"").replace(/\n{3,}/g,`
5
+ `),process.exit(1));Ge=He({token:Se})});import"dotenv/config";import Je from"puppeteer";import x from"fs";import P from"path";var Ee="cookies.json";function K(){let e=process.env.ILML_SCOPE_DIR;return e?P.join(e,"plugins-state","linkedin","cookies.json"):Ee}function be(){let e=K(),o=P.dirname(e);return o&&o!=="."&&!x.existsSync(o)&&x.mkdirSync(o,{recursive:!0}),e}function z(e){let o=be();return x.writeFileSync(o,JSON.stringify(e)),o}function V(){let e=K();if(!x.existsSync(e))throw new Error(`Cookies not found at ${e}. Run: ilml linkedin login`);let o;try{o=x.readFileSync(e,"utf-8")}catch(i){throw new Error(`Failed to read cookies at ${e}: ${i.message}. Run: ilml linkedin login`)}let t;try{t=JSON.parse(o)}catch{throw new Error(`Cookies file at ${e} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(t)||t.length===0)throw new Error(`Cookies file at ${e} is empty or not an array. Run: ilml linkedin login`);return t}function Q(){return x.existsSync(K())}function $e(){let e=process.env.ILML_SCOPE_DIR;return e?P.join(e,"plugins-state","linkedin"):"."}function ee(e){let o=String(e||"").replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,80);return/[a-zA-Z0-9]/.test(o)?o:"default"}function Le(){let e=process.env.MY_LINKEDIN_URL,o=e&&e.match(/\/in\/([^/?#]+)/);return o&&o[1]?ee(o[1]):"default"}function te(e){let o=process.env.LINKEDIN_CHROME_PROFILE_DIR;if(o)return x.existsSync(o)||x.mkdirSync(o,{recursive:!0}),P.resolve(o);let t=ee(e||Le()),i=P.join($e(),"chrome-profile",t);return x.existsSync(i)||x.mkdirSync(i,{recursive:!0}),P.resolve(i)}var oe=".ilml-seeded";function ne(e){return!x.existsSync(P.join(e,oe))}function se(e){try{x.writeFileSync(P.join(e,oe),new Date().toISOString())}catch{}}function re(e){let o=0;for(let t of["SingletonLock","SingletonCookie","SingletonSocket"])try{x.rmSync(P.join(e,t),{force:!0}),o++}catch{}return o}async function Oe(e,o,{timeout:t=45e3,poll:i=1e3,label:l="page"}={}){let r=Date.now();for(;Date.now()-r<t;){try{if(await e.evaluate(h=>!!document.querySelector(h),o)){let h=Date.now()-r;return h>6e3&&console.log(` (waited ${Math.round(h/1e3)}s for LinkedIn to finish loading ${l})`),!0}}catch{}await new Promise(d=>setTimeout(d,i))}return!1}var ae=/\/authwall|\/login|\/checkpoint|\/uas\/login/;function De(e){return ae.test(e||"")}async function Re(e,{credentialWaitMs:o=36e4,label:t="page"}={}){console.log(` \u26A0 Auth wall on ${t} \u2014 attempting self-recovery (cookies may just need a refresh)...`);try{await e.goto("https://www.linkedin.com/feed/",{waitUntil:"domcontentloaded",timeout:6e4})}catch{}let i=!!(process.stdin&&process.stdin.isTTY),l=i?o:Math.min(o,45e3),r=Date.now(),d=!1;for(;Date.now()-r<l;){let n="wait";try{n=await e.evaluate(p=>!new RegExp(p).test(location.href)&&document.querySelector("#global-nav, nav.global-nav")?"in":document.querySelector('input#username, input[name="session_key"], input#password')?"creds":"wait",ae.source)}catch{}if(n==="in"){try{let p=await e.cookies();if(Array.isArray(p)&&p.some(k=>k.name==="li_at")){let k=z(p);console.log(` \u2713 Session recovered \u2014 re-saved ${p.length} cookies (${k}).`)}else console.log(" \u2713 Session recovered (kept existing cookie snapshot \u2014 refreshed set had no li_at).")}catch(p){console.log(` \u2713 Session recovered (cookie re-save skipped: ${p.message}).`)}return!0}if(n==="creds"){if(!i){let p=new Error(`Session expired (${t}) \u2014 credentials required and no interactive terminal. Run: ilml linkedin login`);throw p.code="SESSION_EXPIRED",p}d||(d=!0,console.log(`
6
+ \u26A0 LinkedIn is asking for your login + password (cookies not enough this time).`),console.log(" \u2192 Sign in MANUALLY in the browser window that is already open."),console.log(` I will wait and resume automatically \u2014 do NOT close the window or re-run the command.
7
+ `))}await new Promise(p=>setTimeout(p,2500))}let h=new Error(`Session expired (${t}) \u2014 not signed in within the wait window. Run: ilml linkedin login`);throw h.code="SESSION_EXPIRED",h}async function ie(e,{readySelector:o,label:t="page",timeout:i=45e3}={}){let l=await Oe(e,o,{timeout:i,label:t});if(!l&&De(e.url())){let r=new Error(`Session expired (${t}) \u2014 redirected to login. Run: ilml linkedin login`);throw r.code="SESSION_EXPIRED",r}return l}async function ce(e,o,{readySelector:t,label:i="page",timeout:l=45e3,gotoTimeout:r=6e4,autoRecover:d=!0}={}){await e.goto(o,{waitUntil:"domcontentloaded",timeout:r});try{return await ie(e,{readySelector:t,label:i,timeout:l})}catch(h){if(d&&h.code==="SESSION_EXPIRED"){await Re(e,{label:i});try{await e.goto(o,{waitUntil:"domcontentloaded",timeout:r})}catch{}return await ie(e,{readySelector:t,label:i,timeout:l})}throw h}}var W=e=>new Promise(o=>setTimeout(o,e));function le(e){return e?e.split("?")[0].split("#")[0]:""}async function ue(e,o,t={}){let i=t.commentPasses??4,l=t.skipComments??!1,r=Number.isFinite(t.commentsMax)?t.commentsMax:1/0;try{await ce(e,o,{readySelector:"main, .scaffold-layout__main",label:"post",timeout:45e3})}catch(s){if(s.code==="SESSION_EXPIRED")return{authWall:!0};throw s}if(await W(1e3),await e.evaluate(()=>/\/login|\/authwall|\/checkpoint|\/uas\//.test(location.pathname)||!!document.querySelector('input[name="session_key"]')))return{authWall:!0};let h=l?2:6;for(let s=0;s<h;s++)await e.evaluate(()=>window.scrollBy(0,1200)),await W(l?500:1e3);if(l)await e.evaluate(()=>{for(let s of document.querySelectorAll("button")){let g=`${s.innerText||""} ${s.getAttribute("aria-label")||""}`;if(/^(see more|…more|show more)$/i.test(g.trim()))try{s.click()}catch{}}}),await W(500);else for(let s=0;s<i;s++){let g=await e.evaluate(()=>{let c=0;for(let m of document.querySelectorAll("button")){let S=`${m.innerText||""} ${m.getAttribute("aria-label")||""}`;if(/see more|…more|show more|load more comments|more comments|previous replies|more replies|показать ещё|ещё|загрузить ещё|показать предыдущие/i.test(S))try{m.click(),c++}catch{}}return c});if(await W(1800),!g)break}let n=await e.evaluate(()=>{let s=(w,y)=>{for(let I of y){let C=w&&w.querySelector(I);if(C&&(C.innerText||"").trim())return C.innerText.trim()}return""},g=(w,y)=>{for(let I of y){let C=w&&w.querySelector(I);if(C&&C.href)return C.href}return""},c=document.querySelector("main")||document.body,m=document.querySelector(".fie-impression-container")||document.querySelector(".feed-shared-update-v2")||c,S={name:s(m,['.update-components-actor__title span[aria-hidden="true"]','.update-components-actor__name span[aria-hidden="true"]',".update-components-actor__title",".update-components-actor__name"]),profileUrl:g(m,['.update-components-actor__container a[href*="/in/"]','.update-components-actor__meta a[href*="/in/"]','a.app-aware-link[href*="/in/"]']),headline:s(m,[".update-components-actor__description"])},$=s(m,[".update-components-update-v2__commentary",".feed-shared-update-v2__description",".update-components-text"]),F=s(m,['.update-components-actor__sub-description span[aria-hidden="true"]',".update-components-actor__sub-description"]),N=s(document,[".social-details-social-counts__reactions-count",".social-details-social-counts__social-proof-fallback-number"]),U=s(document,['button[aria-label*="comment" i] span',".social-details-social-counts__comments"]),A=s(document,['button[aria-label*="repost" i] span',".social-details-social-counts__reposts",".social-details-social-counts__shares"]),f=m.querySelector(".update-components-update-v2__commentary")||m.querySelector(".update-components-text")||m,L=Array.from(f?.querySelectorAll?.('a[href*="/in/"], a[href*="/company/"]')||[]),b=[],j=new Set;for(let w of L){let y=(w.href||"").split("?")[0].replace(/\/$/,"");if(!y||j.has(y))continue;j.add(y);let I=(w.textContent||"").trim(),C=y.includes("/company/")?"company":"person";b.push({kind:C,url:y,label:I||null})}let q=Array.from(new Set((($||"")+" "+(f?.textContent||"")).match(/#[\p{L}\p{N}_]{2,40}/gu)||[])),J={hasImage:!!m.querySelector(".update-components-image, img.feed-shared-image__image"),hasVideo:!!m.querySelector("video, .update-components-video, .feed-shared-update-v2__content video"),hasArticle:!!m.querySelector(".update-components-article, .feed-shared-article"),hasDocument:!!m.querySelector(".feed-shared-document, .update-components-document"),hasPoll:!!m.querySelector(".update-components-poll, .feed-shared-poll")},H=document.querySelectorAll("article.comments-comment-entity, .comments-comment-entity, .comments-comment-item"),u=[];for(let w of H){let y=s(w,[".comments-comment-meta__description-title",'.comments-post-meta__name-text span[aria-hidden="true"]',".comments-post-meta__name-text"]),I=s(w,[".comments-comment-item__main-content",".comments-comment-entity__content .update-components-text",".comments-comment-entity__content",".update-components-text"]);!y&&!I||u.push({author:{name:y,profileUrl:g(w,['a[href*="/in/"]']),headline:s(w,[".comments-comment-meta__description-subtitle",".comments-post-meta__headline"])},text:I})}let B=new Set,Ae=u.filter(w=>{let y=`${w.author.name}|${w.text.slice(0,80)}`;return B.has(y)?!1:(B.add(y),!0)});return{url:location.href,author:S,text:$,postedAt:F,reactions:N,commentCount:U,repostCount:A,hashtags:q,mentions:b,media:J,comments:Ae,fullText:(c.innerText||"").replace(/\n{3,}/g,`
6
8
 
7
- `)}});p?(n.comments=[],n.commentsTruncated=!0,n.commentsTruncatedReason="skipComments"):Number.isFinite(i)&&n.comments.length>i?(n.commentsTruncated=!0,n.commentsTruncatedReason=`commentsMax=${i}`,n.commentsTruncatedOriginalCount=n.comments.length,n.comments=n.comments.slice(0,i)):n.commentsTruncated=!1,n.author.profileUrl=re(n.author.profileUrl),n.comments=n.comments.map(s=>({...s,author:{...s.author,profileUrl:re(s.author.profileUrl)}}));let S=n.fullText||"";if(!n.author.name){let s=S.match(/\n\s*Feed post\s*\n+\s*([^\n]+)/i);s&&(n.author.name=s[1].trim())}if(!n.reactions){let s=S.match(/([\d,]+)\s+reactions?/i);s&&(n.reactions=s[1])}if(!n.commentCount){let s=S.match(/([\d,]+)\s+comments?/i);s&&(n.commentCount=s[1])}let r=S,b=r.search(/\n\s*Feed post\s*\n/i);if(b>=0&&(r=r.slice(b).replace(/^\s*Feed post\s*\n/i,"")),r=r.replace(/\n\s*About\s*\n+\s*Accessibility[\s\S]*$/i,"").replace(/\n\s*LinkedIn Corporation ©[\s\S]*$/i,"").trim(),n.cleanText=r,n.cleanText){let s=n.cleanText.split(`
8
- `).map(c=>c.trim()),f=s.findIndex(c=>/^\d+\s*[mhdwy]\s*•/i.test(c));if(f>=0&&(n.postedAt||(n.postedAt=s[f].replace(/\s*•\s*$/,"").trim()),!n.text)){let c=[];for(let u=f+1;u<s.length;u++){let y=s[u];if(/^(Most relevant|Top comments|Comments|All comments|\d+ comments?)\b/i.test(y)||/^[\d,]+$/.test(y)&&/^[\d,]+$/.test(s[u+1]||""))break;c.push(y)}n.text=c.join(`
9
+ `)}});l?(n.comments=[],n.commentsTruncated=!0,n.commentsTruncatedReason="skipComments"):Number.isFinite(r)&&n.comments.length>r?(n.commentsTruncated=!0,n.commentsTruncatedReason=`commentsMax=${r}`,n.commentsTruncatedOriginalCount=n.comments.length,n.comments=n.comments.slice(0,r)):n.commentsTruncated=!1,n.author.profileUrl=le(n.author.profileUrl),n.comments=n.comments.map(s=>({...s,author:{...s.author,profileUrl:le(s.author.profileUrl)}}));let p=n.fullText||"";if(!n.author.name){let s=p.match(/\n\s*Feed post\s*\n+\s*([^\n]+)/i);s&&(n.author.name=s[1].trim())}if(!n.reactions){let s=p.match(/([\d,]+)\s+reactions?/i);s&&(n.reactions=s[1])}if(!n.commentCount){let s=p.match(/([\d,]+)\s+comments?/i);s&&(n.commentCount=s[1])}let a=p,k=a.search(/\n\s*Feed post\s*\n/i);if(k>=0&&(a=a.slice(k).replace(/^\s*Feed post\s*\n/i,"")),a=a.replace(/\n\s*About\s*\n+\s*Accessibility[\s\S]*$/i,"").replace(/\n\s*LinkedIn Corporation ©[\s\S]*$/i,"").trim(),n.cleanText=a,n.cleanText){let s=n.cleanText.split(`
10
+ `).map(c=>c.trim()),g=s.findIndex(c=>/^\d+\s*[mhdwy]\s*•/i.test(c));if(g>=0&&(n.postedAt||(n.postedAt=s[g].replace(/\s*•\s*$/,"").trim()),!n.text)){let c=[];for(let m=g+1;m<s.length;m++){let S=s[m];if(/^(Most relevant|Top comments|Comments|All comments|\d+ comments?)\b/i.test(S)||/^[\d,]+$/.test(S)&&/^[\d,]+$/.test(s[m+1]||""))break;c.push(S)}n.text=c.join(`
9
11
  `).replace(/\n{3,}/g,`
10
12
 
11
- `).trim()}}if(!p&&n.comments.length===0&&n.cleanText){let s=n.cleanText.split(`
12
- `).map(c=>c.trim()),f=s.findIndex(c=>/^(Most relevant|Top comments|Recent comments)$/i.test(c));if(f>=0){let c=s.slice(f+1),u=[],y=[];for(let d of c){if(/^\d+\s+reactions?/i.test(d)){y.length>0&&u.push(y),y=[];continue}y.push(d)}y.length>0&&u.push(y);let E=d=>/Premium Profile|Verified Profile/i.test(d),M=d=>/[·•]\s*\d+(?:st|nd|rd|th)\+?/i.test(d),F=d=>/^\d+$/.test(d),N=d=>/^(Reactions|Reply|Like|Author|See translation|Show \d+)$/i.test(d),P=[];for(let d of u){let $=d.findIndex(l=>l==="Follow");if($<0)continue;let T=(d.find(l=>l&&!E(l)&&!M(l)&&!F(l)&&!N(l))||"").trim(),U=d.find(l=>/^\d+[hmsdwy]$/.test(l))||"",j=d.slice(0,$).filter(l=>l&&l!==T&&l!==U&&!E(l)&&!M(l)&&!F(l)&&!N(l));j.sort((l,H)=>H.length-l.length);let J=j[0]||"",q=d.slice($+1).filter(l=>l&&l!=="Reply"&&l!=="Like"&&l!=="Reactions").join(`
13
- `).trim();T&&q&&P.push({author:{name:T,headline:J,profileUrl:""},text:q})}if(P.length>0){let d=Number.isFinite(i)?P.slice(0,i):P;n.comments=d,n.commentsFromCleanTextFallback=!0,d.length<P.length&&(n.commentsTruncated=!0,n.commentsTruncatedReason=`commentsMax=${i}`,n.commentsTruncatedOriginalCount=P.length)}}}if(n.text&&Array.isArray(n.mentions)&&n.mentions.length>0){let s=n.text.toLowerCase(),f=n.mentions.filter(c=>c.label?s.includes(c.label.toLowerCase()):!1);n.mentionsFilteredFromDOM=n.mentions.length-f.length,n.mentions=f}let R=o.match(/(?:activity-|share-)(\d{6,})/);return n.urn=R?R[1]:"",n}import K from"fs";import be from"path";var Ee={headless:!1,defaultViewport:null,protocolTimeout:3e5},O=new Set,ce=!1;async function W(){let e=[...O].map(o=>{try{return Promise.resolve(o.close()).catch(()=>{})}catch{return Promise.resolve()}});O.clear(),await Promise.race([Promise.all(e),new Promise(o=>setTimeout(o,5e3))])}function $e(){if(ce)return;ce=!0;let e=o=>async()=>{await W(),process.exit(o)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async o=>{await W(),console.error(o&&o.stack||o),process.exit(1)}),process.once("unhandledRejection",async o=>{await W(),console.error(o),process.exit(1)}),process.stdout.on("error",async o=>{o&&o.code==="EPIPE"&&(await W(),process.exit(0))}),process.on("exit",()=>{for(let o of O)try{let t=o.process&&o.process();t&&t.kill("SIGKILL")}catch{}})}function Y(e){return be.join(e,".ilml-browser-session.json")}function me(e,o={}){try{K.writeFileSync(Y(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...o}))}catch{}}function Le(e,o){try{let t=o.wsEndpoint&&o.wsEndpoint();me(e,t?{wsEndpoint:t}:{})}catch{}}function Oe(e){let o=pe(e);o&&o.ownerPid===process.pid&&L(e)}function L(e){try{K.rmSync(Y(e),{force:!0})}catch{}}function pe(e){try{return JSON.parse(K.readFileSync(Y(e),"utf-8"))}catch{return null}}function le(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(o){return!o||o.code!=="ESRCH"}}function ue(e,o,t){let a,p=new Promise(m=>{a=setTimeout(()=>m(t),o)}),i=Promise.resolve(e).then(m=>(clearTimeout(a),m),m=>{throw clearTimeout(a),m});return Promise.race([i,p])}async function de(e,o,{probeTimeoutMs:t=4e3}={}){let a=pe(o);if(!a)return"free";if(!a.wsEndpoint)return a.ownerPid&&a.ownerPid!==process.pid&&le(a.ownerPid)?"live":(L(o),"free");let p=Symbol("timeout"),i=e.connect({browserWSEndpoint:a.wsEndpoint,defaultViewport:null}).catch(()=>null),m=await ue(i,t,p);if(m===p)return i.then(n=>{if(n)try{n.disconnect()}catch{}}),"live";if(!m)return L(o),"free";if(le(a.ownerPid)){try{m.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await ue(m.close().then(()=>!0).catch(()=>!1),t,!1))return L(o),"free";try{m.disconnect()}catch{}return"live"}function De(e,o){e&&($e(),O.add(e),e.on("disconnected",()=>{O.delete(e),o&&L(o)}))}async function fe(e,o={}){let t=ee(),a={...Ee,...o,userDataDir:t},p=`Could not open the LinkedIn automation Chrome profile at ${t}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await de(e,t)==="live")throw new Error(p);me(t);let i;try{i=await e.launch(a)}catch{if(await de(e,t)==="live")throw new Error(p);se(t);try{i=await e.launch(a)}catch(g){throw Oe(t),new Error(`Could not open the LinkedIn automation Chrome profile at ${t}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${g.message})`)}}return De(i,t),Le(t,i),{browser:i,profileDir:t,needsSeed:oe(t)}}async function he(e,o){if(!o||!o.needsSeed)return!1;if(!V())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...z()),ne(o.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var _=process.argv.slice(2),je=_.includes("--help")||_.includes("-h"),xe=_.includes("--json"),D=_.find(e=>e.startsWith("--save-to-graph="))?.split("=")[1]||null,X=_.find(e=>e.startsWith("--out="))?.split("=")[1]||null,qe=_.includes("--no-comments"),Se=_.find(e=>e.startsWith("--comments-max="))?.split("=")[1],He=Se!=null?parseInt(Se,10):null,I=_.find(e=>e.startsWith("http")),C=xe?(...e)=>console.error(...e):(...e)=>console.log(...e);function ke(){console.log(`
13
+ `).trim()}}if(!l&&n.comments.length===0&&n.cleanText){let s=n.cleanText.split(`
14
+ `).map(c=>c.trim()),g=s.findIndex(c=>/^(Most relevant|Top comments|Recent comments)$/i.test(c));if(g>=0){let c=s.slice(g+1),m=[],S=[];for(let f of c){if(/^\d+\s+reactions?/i.test(f)){S.length>0&&m.push(S),S=[];continue}S.push(f)}S.length>0&&m.push(S);let $=f=>/Premium Profile|Verified Profile/i.test(f),F=f=>/[·•]\s*\d+(?:st|nd|rd|th)\+?/i.test(f),N=f=>/^\d+$/.test(f),U=f=>/^(Reactions|Reply|Like|Author|See translation|Show \d+)$/i.test(f),A=[];for(let f of m){let L=f.findIndex(u=>u==="Follow");if(L<0)continue;let b=(f.find(u=>u&&!$(u)&&!F(u)&&!N(u)&&!U(u))||"").trim(),j=f.find(u=>/^\d+[hmsdwy]$/.test(u))||"",q=f.slice(0,L).filter(u=>u&&u!==b&&u!==j&&!$(u)&&!F(u)&&!N(u)&&!U(u));q.sort((u,B)=>B.length-u.length);let J=q[0]||"",H=f.slice(L+1).filter(u=>u&&u!=="Reply"&&u!=="Like"&&u!=="Reactions").join(`
15
+ `).trim();b&&H&&A.push({author:{name:b,headline:J,profileUrl:""},text:H})}if(A.length>0){let f=Number.isFinite(r)?A.slice(0,r):A;n.comments=f,n.commentsFromCleanTextFallback=!0,f.length<A.length&&(n.commentsTruncated=!0,n.commentsTruncatedReason=`commentsMax=${r}`,n.commentsTruncatedOriginalCount=A.length)}}}if(n.text&&Array.isArray(n.mentions)&&n.mentions.length>0){let s=n.text.toLowerCase(),g=n.mentions.filter(c=>c.label?s.includes(c.label.toLowerCase()):!1);n.mentionsFilteredFromDOM=n.mentions.length-g.length,n.mentions=g}let M=o.match(/(?:activity-|share-)(\d{6,})/);return n.urn=M?M[1]:"",n}import Y from"fs";import Me from"path";var Fe={headless:!1,defaultViewport:null,protocolTimeout:3e5},D=new Set,de=!1;async function G(){let e=[...D].map(o=>{try{return Promise.resolve(o.close()).catch(()=>{})}catch{return Promise.resolve()}});D.clear(),await Promise.race([Promise.all(e),new Promise(o=>setTimeout(o,5e3))])}function Ne(){if(de)return;de=!0;let e=o=>async()=>{await G(),process.exit(o)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async o=>{await G(),console.error(o&&o.stack||o),process.exit(1)}),process.once("unhandledRejection",async o=>{await G(),console.error(o),process.exit(1)}),process.stdout.on("error",async o=>{o&&o.code==="EPIPE"&&(await G(),process.exit(0))}),process.on("exit",()=>{for(let o of D)try{let t=o.process&&o.process();t&&t.kill("SIGKILL")}catch{}})}function X(e){return Me.join(e,".ilml-browser-session.json")}function he(e,o={}){try{Y.writeFileSync(X(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...o}))}catch{}}function Ue(e,o){try{let t=o.wsEndpoint&&o.wsEndpoint();he(e,t?{wsEndpoint:t}:{})}catch{}}function je(e){let o=ge(e);o&&o.ownerPid===process.pid&&O(e)}function O(e){try{Y.rmSync(X(e),{force:!0})}catch{}}function ge(e){try{return JSON.parse(Y.readFileSync(X(e),"utf-8"))}catch{return null}}function me(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(o){return!o||o.code!=="ESRCH"}}function pe(e,o,t){let i,l=new Promise(d=>{i=setTimeout(()=>d(t),o)}),r=Promise.resolve(e).then(d=>(clearTimeout(i),d),d=>{throw clearTimeout(i),d});return Promise.race([r,l])}async function fe(e,o,{probeTimeoutMs:t=4e3}={}){let i=ge(o);if(!i)return"free";if(!i.wsEndpoint)return i.ownerPid&&i.ownerPid!==process.pid&&me(i.ownerPid)?"live":(O(o),"free");let l=Symbol("timeout"),r=e.connect({browserWSEndpoint:i.wsEndpoint,defaultViewport:null}).catch(()=>null),d=await pe(r,t,l);if(d===l)return r.then(n=>{if(n)try{n.disconnect()}catch{}}),"live";if(!d)return O(o),"free";if(me(i.ownerPid)){try{d.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await pe(d.close().then(()=>!0).catch(()=>!1),t,!1))return O(o),"free";try{d.disconnect()}catch{}return"live"}function qe(e,o){e&&(Ne(),D.add(e),e.on("disconnected",()=>{D.delete(e),o&&O(o)}))}async function we(e,o={}){let t=te(),i={...Fe,...o,userDataDir:t},l=`Could not open the LinkedIn automation Chrome profile at ${t}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await fe(e,t)==="live")throw new Error(l);he(t);let r;try{r=await e.launch(i)}catch{if(await fe(e,t)==="live")throw new Error(l);re(t);try{r=await e.launch(i)}catch(h){throw je(t),new Error(`Could not open the LinkedIn automation Chrome profile at ${t}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${h.message})`)}}return qe(r,t),Ue(t,r),{browser:r,profileDir:t,needsSeed:ne(t)}}async function ye(e,o){if(!o||!o.needsSeed)return!1;if(!Q())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...V()),se(o.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var _=process.argv.slice(2),Ke=_.includes("--help")||_.includes("-h"),Pe=_.includes("--json"),R=_.find(e=>e.startsWith("--save-to-graph="))?.split("=")[1]||null,Z=_.find(e=>e.startsWith("--out="))?.split("=")[1]||null,Ye=_.includes("--no-comments"),Ce=_.find(e=>e.startsWith("--comments-max="))?.split("=")[1],Xe=Ce!=null?parseInt(Ce,10):null,E=_.find(e=>e.startsWith("http")),v=Pe?(...e)=>console.error(...e):(...e)=>console.log(...e);function ve(){console.log(`
14
16
  read-post \u2014 read ONE LinkedIn post by URL (analysis-first; saving is opt-in)
15
17
 
16
18
  DEFAULT USE CASE
@@ -71,6 +73,6 @@ WHAT YOU GET ON ONE PAGE LOAD (no extra cost regardless of flags)
71
73
  BAN RISK
72
74
  Low. One post view = a single page load = normal human browsing.
73
75
  --no-comments is even quieter (skips the load-more-comments scroll loop).
74
- `)}je&&(ke(),process.exit(0));I||(ke(),process.exit(1));/linkedin\.com\/(posts|feed|in)\/|linkedin\.com\/.*(activity|update)/i.test(I)||(console.error(`URL doesn't look like a LinkedIn post: ${I}`),console.error("Expected something like:"),console.error(" https://www.linkedin.com/posts/<slug>-activity-<id>-xxxx"),console.error(" https://www.linkedin.com/feed/update/urn:li:activity:<id>"),process.exit(1));C("");C(` read-post \u2014 ${I}`);C("");var G=null;try{let e=await fe(Ue);G=e.browser;let o=await G.newPage();await he(o,e);let t=await ae(o,I,{skipComments:qe,commentsMax:He});t.authWall&&(console.error(" \u26D4 Auth wall \u2014 cookies expired or not logged in."),console.error(" Run 'ilml linkedin login' (or: node src/saveLoginState.mjs) and retry."),process.exit(2));let a=null;if(D){let i=t.author||{},m=t.text||t.cleanText||t.fullText||"",g=(m.replace(/\s+/g," ").trim()||"(empty post)").slice(0,80),n=`${i.name||"unknown"} \u2014 ${g}${g.length===80?"\u2026":""}`,S={url:t.url||I,urn:t.urn||null,author:t.author||null,text:t.text||null,postedAt:t.postedAt||null,hashtags:t.hashtags||[],mentions:t.mentions||[],media:t.media||null,capturedAt:new Date().toISOString()},r=[];i.name&&r.push(`**Author:** ${i.name}${i.headline?` \u2014 ${i.headline}`:""}`),i.profileUrl&&r.push(`**Profile:** ${i.profileUrl}`),t.postedAt&&r.push(`**Posted:** ${t.postedAt}`),r.push(`**Source:** ${I}`),r.push(`**Captured:** ${S.capturedAt.slice(0,10)}`),r.push(""),r.push("---"),r.push(""),r.push(m||"(post text not extracted)"),t.hashtags?.length&&(r.push(""),r.push(`**Hashtags:** ${t.hashtags.join(" ")}`)),t.mentions?.length&&(r.push(""),r.push(`**Mentions:** ${t.mentions.map(s=>`[${s.label||s.kind}](${s.url})`).join(", ")}`));let b=Object.entries(t.media||{}).filter(([,s])=>s).map(([s])=>s.replace(/^has/,"").toLowerCase());b.length&&(r.push(""),r.push(`**Media:** ${b.join(", ")}`)),r.push(""),r.push("---"),r.push(""),r.push("*Live engagement (reactions / comments / reposts) is intentionally NOT saved here \u2014 those drift. Re-run `read-post --json` on the source URL for current numbers.*"),r.push(""),r.push("```json"),r.push(JSON.stringify(S,null,2)),r.push("```");let R=r.join(`
75
- `);try{let{graph:s}=await Promise.resolve().then(()=>(ye(),we)),f=await s.addItem(D,{title:n,description:R,tags:["linkedin-post"]}),c=f?.id||f?.item?.id||null;a={parentId:D,nodeId:c,url:c?`https://app.ilivemylife.io/item/${c}`:null},C(""),C(` \u2713 Saved to graph as child of ${D}`),C(` New node: ${c||"(id not returned by SDK)"}`),c&&C(` URL: https://app.ilivemylife.io/item/${c}`),C("")}catch(s){a={parentId:D,error:s.message?.slice(0,200)},console.error(""),console.error(` \u26A0 Failed to save to graph: ${s.message?.slice(0,200)}`),console.error(" The post was still extracted successfully \u2014 printing below."),console.error("")}}let p={...t,...a!==null&&{savedToGraph:a}};if(X&&((await import("fs")).writeFileSync(X,JSON.stringify(p,null,2)),C(` \u2713 Wrote ${(JSON.stringify(p).length/1024).toFixed(1)}KB to ${X}`)),xe)console.log(JSON.stringify(p,null,2));else{let i=t.author||{};console.log(" \u2500\u2500\u2500 POST \u2500\u2500\u2500"),console.log(` Author: ${i.name||"(unknown)"}${i.headline?` \u2014 ${i.headline}`:""}`),i.profileUrl&&console.log(` Profile: ${i.profileUrl}`),t.postedAt&&console.log(` Posted: ${t.postedAt}`);let m=[`${t.reactions||"0"} reactions`,`${t.commentCount||t.comments.length||"0"} comments`,t.repostCount?`${t.repostCount} reposts`:null].filter(Boolean).join(" ");console.log(` Engagement: ${m}`);let g=Object.entries(t.media||{}).filter(([,n])=>n).map(([n])=>n.replace(/^has/,"").toLowerCase());if(g.length&&console.log(` Media: ${g.join(", ")}`),t.hashtags?.length&&console.log(` Hashtags: ${t.hashtags.join(" ")}`),t.mentions?.length&&console.log(` Mentions: ${t.mentions.map(n=>`${n.label||n.url} (${n.kind})`).join(", ")}`),console.log(""),t.commentsTruncated){let n=t.commentsTruncatedReason==="skipComments"?"(comments skipped via --no-comments)":`(showing first ${t.comments.length} of ${t.commentsTruncatedOriginalCount} \u2014 capped via --comments-max)`;console.log(` Note: ${n}`),console.log("")}if(t.comments.length){console.log(t.text||"(post text not parsed \u2014 see cleanText below)"),console.log(""),console.log(` \u2500\u2500\u2500 COMMENTS (${t.comments.length}) \u2500\u2500\u2500`);for(let n of t.comments){let S=n.author||{};console.log(""),console.log(` \u2022 ${S.name||"(unknown)"}${S.headline?` \u2014 ${S.headline}`:""}`),n.text&&console.log(` ${n.text.replace(/\n+/g,`
76
- `)}`)}}else console.log(t.cleanText||t.fullText||"(no content extracted)");console.log(""),console.log(" (use --json for the full structured object)")}}catch(e){console.error(" Error:",e.message),process.exit(1)}finally{G&&await G.close().catch(()=>{})}
76
+ `)}Ke&&(ve(),process.exit(0));E||(ve(),process.exit(1));/linkedin\.com\/(posts|feed|in)\/|linkedin\.com\/.*(activity|update)/i.test(E)||(console.error(`URL doesn't look like a LinkedIn post: ${E}`),console.error("Expected something like:"),console.error(" https://www.linkedin.com/posts/<slug>-activity-<id>-xxxx"),console.error(" https://www.linkedin.com/feed/update/urn:li:activity:<id>"),process.exit(1));v("");v(` read-post \u2014 ${E}`);v("");var T=null;try{let e=await we(Je);T=e.browser;let o=await T.newPage();await ye(o,e);let t=await ue(o,E,{skipComments:Ye,commentsMax:Xe});if(t.authWall){console.error(" \u26D4 Auth wall \u2014 cookies expired or not logged in."),console.error(" Run 'ilml linkedin login' (or: node src/saveLoginState.mjs) and retry.");try{T&&await T.close()}catch{}T=null,process.exit(2)}let i=null;if(R){let r=t.author||{},d=t.text||t.cleanText||t.fullText||"",h=(d.replace(/\s+/g," ").trim()||"(empty post)").slice(0,80),n=`${r.name||"unknown"} \u2014 ${h}${h.length===80?"\u2026":""}`,p={url:t.url||E,urn:t.urn||null,author:t.author||null,text:t.text||null,postedAt:t.postedAt||null,hashtags:t.hashtags||[],mentions:t.mentions||[],media:t.media||null,capturedAt:new Date().toISOString()},a=[];r.name&&a.push(`**Author:** ${r.name}${r.headline?` \u2014 ${r.headline}`:""}`),r.profileUrl&&a.push(`**Profile:** ${r.profileUrl}`),t.postedAt&&a.push(`**Posted:** ${t.postedAt}`),a.push(`**Source:** ${E}`),a.push(`**Captured:** ${p.capturedAt.slice(0,10)}`),a.push(""),a.push("---"),a.push(""),a.push(d||"(post text not extracted)"),t.hashtags?.length&&(a.push(""),a.push(`**Hashtags:** ${t.hashtags.join(" ")}`)),t.mentions?.length&&(a.push(""),a.push(`**Mentions:** ${t.mentions.map(s=>`[${s.label||s.kind}](${s.url})`).join(", ")}`));let k=Object.entries(t.media||{}).filter(([,s])=>s).map(([s])=>s.replace(/^has/,"").toLowerCase());k.length&&(a.push(""),a.push(`**Media:** ${k.join(", ")}`)),a.push(""),a.push("---"),a.push(""),a.push("*Live engagement (reactions / comments / reposts) is intentionally NOT saved here \u2014 those drift. Re-run `read-post --json` on the source URL for current numbers.*"),a.push(""),a.push("```json"),a.push(JSON.stringify(p,null,2)),a.push("```");let M=a.join(`
77
+ `);try{let{graph:s}=await Promise.resolve().then(()=>(ke(),xe)),g=await s.addItem(R,{title:n,description:M,tags:["linkedin-post"]}),c=g?.id||g?.item?.id||null;i={parentId:R,nodeId:c,url:c?`https://app.ilivemylife.io/item/${c}`:null},v(""),v(` \u2713 Saved to graph as child of ${R}`),v(` New node: ${c||"(id not returned by SDK)"}`),c&&v(` URL: https://app.ilivemylife.io/item/${c}`),v("")}catch(s){i={parentId:R,error:s.message?.slice(0,200)},console.error(""),console.error(` \u26A0 Failed to save to graph: ${s.message?.slice(0,200)}`),console.error(" The post was still extracted successfully \u2014 printing below."),console.error("")}}let l={...t,...i!==null&&{savedToGraph:i}};if(Z&&((await import("fs")).writeFileSync(Z,JSON.stringify(l,null,2)),v(` \u2713 Wrote ${(JSON.stringify(l).length/1024).toFixed(1)}KB to ${Z}`)),Pe)console.log(JSON.stringify(l,null,2));else{let r=t.author||{};console.log(" \u2500\u2500\u2500 POST \u2500\u2500\u2500"),console.log(` Author: ${r.name||"(unknown)"}${r.headline?` \u2014 ${r.headline}`:""}`),r.profileUrl&&console.log(` Profile: ${r.profileUrl}`),t.postedAt&&console.log(` Posted: ${t.postedAt}`);let d=[`${t.reactions||"0"} reactions`,`${t.commentCount||t.comments.length||"0"} comments`,t.repostCount?`${t.repostCount} reposts`:null].filter(Boolean).join(" ");console.log(` Engagement: ${d}`);let h=Object.entries(t.media||{}).filter(([,n])=>n).map(([n])=>n.replace(/^has/,"").toLowerCase());if(h.length&&console.log(` Media: ${h.join(", ")}`),t.hashtags?.length&&console.log(` Hashtags: ${t.hashtags.join(" ")}`),t.mentions?.length&&console.log(` Mentions: ${t.mentions.map(n=>`${n.label||n.url} (${n.kind})`).join(", ")}`),console.log(""),t.commentsTruncated){let n=t.commentsTruncatedReason==="skipComments"?"(comments skipped via --no-comments)":`(showing first ${t.comments.length} of ${t.commentsTruncatedOriginalCount} \u2014 capped via --comments-max)`;console.log(` Note: ${n}`),console.log("")}if(t.comments.length){console.log(t.text||"(post text not parsed \u2014 see cleanText below)"),console.log(""),console.log(` \u2500\u2500\u2500 COMMENTS (${t.comments.length}) \u2500\u2500\u2500`);for(let n of t.comments){let p=n.author||{};console.log(""),console.log(` \u2022 ${p.name||"(unknown)"}${p.headline?` \u2014 ${p.headline}`:""}`),n.text&&console.log(` ${n.text.replace(/\n+/g,`
78
+ `)}`)}}else console.log(t.cleanText||t.fullText||"(no content extracted)");console.log(""),console.log(" (use --json for the full structured object)")}}catch(e){console.error(" Error:",e.message),process.exit(1)}finally{T&&await T.close().catch(()=>{})}