ilml-plugin-linkedin 1.15.0 → 1.16.0

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/dist/visit.mjs CHANGED
@@ -238,14 +238,14 @@ function findEntryContainer(root) {
238
238
  `});import an from"puppeteer";import{addExtra as cn}from"puppeteer-extra";import un from"puppeteer-extra-plugin-stealth";le();import{getPluginConfig as en}from"@ilivemylife/graph-sdk";var tn=await en();for(let[e,t]of Object.entries(tn))process.env[e]===void 0&&(process.env[e]=t);var nn=process.env.LINKEDIN_NAME,G=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Go=Object.freeze(new Set([G.DRAFT,G.CONFIRMED,G.NEEDS_REVIEW])),on=Object.freeze(new Set([G.DRAFT,G.CONFIRMED,G.REJECTED,G.NEEDS_REVIEW]));import it from"fs";import rn from"path";var sn="cookies.json";function ln(){let e=process.env.ILML_SCOPE_DIR;return e?rn.join(e,"plugins-state","linkedin","cookies.json"):sn}function lt(){let e=ln();if(!it.existsSync(e))throw new Error(`Cookies not found at ${e}. Run: ilml linkedin login`);let t;try{t=it.readFileSync(e,"utf-8")}catch(o){throw new Error(`Failed to read cookies at ${e}: ${o.message}. Run: ilml linkedin login`)}let n;try{n=JSON.parse(t)}catch{throw new Error(`Cookies file at ${e} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(n)||n.length===0)throw new Error(`Cookies file at ${e} is empty or not an array. Run: ilml linkedin login`);return n}var at=cn(an);at.use(un());async function ct(){let e=await at.launch({headless:!1,defaultViewport:null,protocolTimeout:3e5}),t=async()=>{console.log(`
239
239
  Closing browser...`);try{await e.close()}catch{}process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t);let n=await e.newPage();await n.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})});try{let o=lt();await n.setCookie(...o),console.log(" Browser launched, cookies loaded.")}catch(o){console.error(`
240
240
  [!] ${o.message}
241
- `),await e.close(),process.exit(1)}return{browser:e,page:n}}le();import"dotenv/config";import*as E from"fs";import*as A from"path";import{fileURLToPath as dn}from"url";var fn=dn(import.meta.url),mn=A.dirname(A.dirname(fn)),U=process.env.DATA_DIR?A.resolve(process.env.DATA_DIR,"collected-profiles"):A.join(mn,"collected-profiles"),$e=A.join(U,"people.json"),fr=A.join(U,"jobs.json"),mr=A.join(U,"quota.json"),pr=A.join(U,"conversations.json"),hr=A.join(U,"sync-state.json"),gr=A.join(U,"application-questions.json"),Ee=A.join(U,"profile-history"),ft=(e,t)=>{let n=parseInt(e,10);return Number.isInteger(n)&&n>=0?n:t},yr=ft(process.env.CONNECT_MAX_DAILY,20),wr=ft(process.env.CONNECT_MAX_WEEKLY,100),oe=null,pn=null,hn=null,gn=null,yn=null,wn=null;function mt(){oe=null,pn=null,hn=null,gn=null,yn=null,wn=null}function pt(){E.existsSync(U)||E.mkdirSync(U,{recursive:!0})}function ht(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";E.writeFileSync(o,n);for(let r=0;r<3;r++)try{E.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function gt(e,t){if(!E.existsSync(e))return null;try{let n=JSON.parse(E.readFileSync(e,"utf-8"));try{E.copyFileSync(e,e+".backup")}catch{}return n}catch(n){console.error(`[!] ${t} is corrupted: ${n.message}`),console.error(` File: ${A.resolve(e)}`);let o=e+".backup";if(E.existsSync(o))try{let r=JSON.parse(E.readFileSync(o,"utf-8"));console.error(` Restored from ${o}`);try{E.copyFileSync(o,e)}catch{}return r}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{E.copyFileSync(e,e+".corrupted")}catch{}return null}}function bn(){return new Date().toISOString().split("T")[0]}var Sn=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge","about","currentCompany","currentSchool","connectionsLevel","mutualConnectionsCount","followers","openToWork","hiring","topSkills","availableSections","experience","featuredLinks","_lastEnrichedAt","featured","education","skillsFull","recommendations","certifications"],vn=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"];function Ae(e){if(!e)return null;let t=e.match(/\/in\/([^/?#]+)/);return t?t[1].toLowerCase():null}function kn(){E.existsSync(Ee)||E.mkdirSync(Ee,{recursive:!0})}function yt(e){let t=Ae(e);return t?A.join(Ee,`${t}.json`):null}function Cn(e){let t=yt(e);return t?gt(t,`profile-history/${Ae(e)}.json`)||[]:[]}function xn(e,t){let n=yt(e);if(!n)return;kn();let o=Cn(e);o.unshift(t);try{ht(n,o)}catch(r){console.error(`[!] Failed to append profile-history for ${Ae(e)}: ${r.message}`)}}function ut(e){return!!(e==null||Array.isArray(e)&&e.length===0||typeof e=="string"&&!e.trim())}function dt(e){return Array.isArray(e)?[...e].map(t=>String(t).trim()).sort().join("||"):typeof e=="string"?e.replace(/\s+/g," ").trim():e}function $n(e,t){if(!e)return[];let n=[];for(let o of vn){let r=e[o],s=t[o];ut(r)||ut(s)||dt(r)!==dt(s)&&n.push(o)}return n}function En(e){let t=new Set(["seenCount","sources","firstSeen","lastSeen","lastUpdated","encounters","jobIds","lastFullVisitAt","lastSnapshotAt","messageSent","connectionStatus","connectionDate","connectionMethod"]),n={};for(let[o,r]of Object.entries(e))t.has(o)||(n[o]=r);return n}function ae(){return oe||(pt(),oe=gt($e,"people.json")||{},oe)}function An(e){pt(),oe=e;try{ht($e,e)}catch(t){console.error(`[!] Failed to save people.json: ${t.message}`),console.error(` Path: ${A.resolve($e)}`)}}function _e(e,t={}){if(!e)return null;let n=ae(),o=bn(),r=new Date().toISOString(),s=n[e];if(s){let i=$n(s,t);i.length>0&&(xn(e,{capturedAt:r,previousLastUpdated:s.lastUpdated||null,changedFields:i,snapshot:En(s)}),s.lastSnapshotAt=r),s.seenCount=(s.seenCount||1)+1,s.lastSeen=o,s.lastUpdated=r,t.source&&!s.sources?.includes(t.source)&&(s.sources=s.sources||[],s.sources.push(t.source));for(let c of Sn)if(t[c]!=null){if(Array.isArray(t[c])&&t[c].length===0&&Array.isArray(s[c])&&s[c].length>0)continue;s[c]=t[c]}Array.isArray(t.availableSections)&&(s.lastFullVisitAt=r)}else n[e]={name:t.name||null,title:t.title||null,location:t.location||null,company:t.company||null,about:t.about||null,connectionDegree:t.connectionDegree||null,connectionsCount:t.connectionsCount||null,mutualConnections:t.mutualConnections||null,photoUrl:t.photoUrl||null,hasVerifiedBadge:t.hasVerifiedBadge||!1,currentCompany:t.currentCompany||null,currentSchool:t.currentSchool||null,connectionsLevel:t.connectionsLevel||null,mutualConnectionsCount:t.mutualConnectionsCount||null,followers:t.followers||null,openToWork:t.openToWork||!1,hiring:t.hiring||!1,topSkills:t.topSkills||null,availableSections:t.availableSections||null,experience:t.experience||null,featuredLinks:t.featuredLinks||null,_lastEnrichedAt:t._lastEnrichedAt||null,featured:t.featured||null,education:t.education||null,skillsFull:t.skillsFull||null,recommendations:t.recommendations||null,certifications:t.certifications||null,firstSeen:o,lastSeen:o,seenCount:1,sources:t.source?[t.source]:[],connectionStatus:null,requestedConnectionAt:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:r,lastSnapshotAt:null,lastFullVisitAt:Array.isArray(t.availableSections)?r:null};return An(n),n[e]}async function On(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: npm run login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await $(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function St(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:c=!1,extractCertifications:a=!1}=n;try{await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await $(3e3),await st(e)&&(console.log(" Dismissed Premium overlay"),await $(1500),await On(e,t));let l=0;for(let x=0;x<2;x++){let te=await e.evaluate(()=>document.body.scrollHeight);for(let Y=1;Y<=6;Y++)await e.evaluate(T=>window.scrollTo(0,T),te*Y/6),await $(900);let B=await e.evaluate(()=>document.body.scrollHeight);if(B===l)break;l=B}await $(800);let y=await e.evaluate(()=>{let x=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),te=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),B=p=>{let w=(p||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(w)},Y={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},T=document.querySelector("main");if(!T)return Y;let ke=[...Array.from(T.querySelectorAll("h1")),...Array.from(T.querySelectorAll("h2"))],b=Array.from(T.querySelectorAll("h2")),I=ke.find(p=>{let w=p.textContent&&p.textContent.trim(),S=(w||"").replace(/\s*\(\d+\)$/,"").trim();return w&&w.length>1&&w.length<100&&!x.has(S)&&!te.has(S)}),ne=I?I.textContent.trim():null,D=I?I.parentElement:null;for(;D&&D!==T&&!Array.from(D.children).filter(w=>w.tagName==="P").some(w=>{let S=w.textContent&&w.textContent.trim();return S&&S.length>5&&!B(S)});)D=D.parentElement;let he=null,Je=null,We=null,Ce=null;if(D&&D!==T){let p=Array.from(D.children).filter(S=>S.tagName==="P").filter(S=>{let H=S.textContent&&S.textContent.trim();return H&&H.length>5&&!B(H)});if(p[0]&&(he=p[0].textContent.trim()),p[1]){let S=p[1].textContent.trim().split(/\s*[·•]\s*/);Je=S[0]||null,S.length>1&&(We=S.slice(1).join(" \xB7 "))}let w=Array.from(D.children).filter(S=>S.tagName==="DIV");for(let S=w.length-1;S>=0;S--){let H=w[S].textContent&&w[S].textContent.trim();if(H&&!(ne&&H.startsWith(ne))&&(Ce=H.split(/[·•]/)[0].trim(),Ce))break}}let Ye=null;if(D){let p=D.querySelectorAll("p, span, div");for(let w of p){let S=(w.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){Ye=S;break}}}let Wt=!!T.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),Ge=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Yt=Ge&&Ge.src||null,ge=T.textContent||"",Ke=null,ze=ge.match(/(\d+\+?)\s+connections\b/i);ze&&(Ke=ze[1]+" connections");let ye=null,Qe=ge.match(/(\d+)\s+mutual connection/i),Ze=ge.match(/and\s+(\d+)\s+other mutual/i),Gt=ge.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);Qe?ye=parseInt(Qe[1],10):Ze?ye=2+parseInt(Ze[1],10):Gt&&(ye=2);let Xe=null,et=null,tt=b.find(p=>p.textContent&&p.textContent.trim()==="About");if(tt){let p=tt.parentElement;for(;p&&p.tagName!=="SECTION";)p=p.parentElement;if(p){let w=(p.textContent||"").trim();w=w.replace(/^About\s*/,"").trim();let S=w.match(/Top skills\s*(.+?)$/i);if(S){let xe=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Zt=>Zt.trim()).filter(Boolean);xe.length>0&&xe.length<10&&(et=xe)}w=w.split(/\n?…\s*more/i)[0].trim(),w=w.split(/\n?\.\.\.\s*more/i)[0].trim(),w=w.split(/\bTop skills\b/i)[0].trim(),w=w.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),Xe=w||null}}let nt=null,ot=b.find(p=>p.textContent&&p.textContent.trim()==="Activity");if(ot){let p=ot.parentElement;for(;p&&p.tagName!=="SECTION";)p=p.parentElement;if(p){let w=(p.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);w&&(nt=parseInt(w[1].replace(/,/g,""),10))}}let Kt=[...new Set(b.map(p=>(p.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(p=>p&&p.length>1&&p.length<60&&p!==ne).filter(p=>x.has(p)))],se=document.body.textContent||"",zt=/#OpenToWork\b/i.test(se)||/\bOpen to work\b/.test(se),Qt=/#Hiring\b/i.test(se)||/\bI'?m hiring\b/i.test(se)||/\bWe are hiring\b/i.test(se);return{name:ne,title:he,location:Ce,currentCompany:Je,currentSchool:We,connectionDegree:Ye,hasVerifiedBadge:Wt,photoUrl:Yt,connectionsLevel:Ke,mutualConnectionsCount:ye,followers:nt,openToWork:zt,hiring:Qt,about:Xe,topSkills:et,availableSections:Kt}});console.log(`Viewed: ${y.name} - ${y.title}`);let u=null,f=null,C=null,v=null,_=null,h=null;if(o||r||s||i||c||a){let x=await Promise.resolve().then(()=>(bt(),wt));o&&(u=await x.extractExperienceFromDetails(e,t),console.log(` + Experience: ${u.length} entries`)),r&&(f=await x.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${f.length} entries`)),s&&(C=await x.extractEducationFromDetails(e,t),console.log(` + Education: ${C.length} entries`)),i&&(v=await x.extractSkillsFromDetails(e,t),console.log(` + Skills: ${v.length} entries`)),c&&(_=await x.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${_.length} entries`)),a&&(h=await x.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${h.length} entries`))}return{...y,profileUrl:t,...u!==null&&{experience:u},...f!==null&&{featured:f},...C!==null&&{education:C},...v!==null&&{skillsFull:v},..._!==null&&{recommendations:_},...h!==null&&{certifications:h}}}catch(m){console.error(`Error viewing profile ${t}:`,m.message);try{await e.goto("about:blank",{timeout:1e4}),await $(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(m.message||""))throw m;return null}}import"dotenv/config";import*as O from"fs";import*as j from"path";import{fileURLToPath as Nn}from"url";var Fn=Nn(import.meta.url),Ln=j.dirname(j.dirname(Fn)),z=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(Ln,"market-research"),Mn=j.join(z,"scouted-jobs.json"),Pn=j.join(z,"discovered-people.json"),vt=j.join(z,"visit-log.json"),Rr=j.join(z,"companies.json"),jr=j.join(z,"job-decisions.json");function qn(){O.existsSync(z)||O.mkdirSync(z,{recursive:!0})}function Bn(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";O.writeFileSync(o,n);for(let r=0;r<3;r++)try{O.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function Ie(e){try{if(O.existsSync(e))return JSON.parse(O.readFileSync(e,"utf-8"))}catch{}return null}function kt(){fe=null,Vn=null,me=null,Hn=null,K=null}var fe=null;function Un(){return fe||(fe=Ie(Mn)||{},fe)}var Vn=null;var me=null;function we(){return me||(me=Ie(Pn)||{},me)}var Hn=null;var K=null;function re(){return K||(K=Ie(vt)||{visits:[]},Array.isArray(K.visits)||(K.visits=[]),K)}function Ct(e,t={}){if(!e)return;qn();let n=re();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{Bn(vt,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function xt(e){let t=re();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function $t(){let e=Un(),t=we(),n=Object.values(e),o=Object.values(t),r={};for(let u of n)u.company&&(r[u.company]=(r[u.company]||0)+1);let s=Object.entries(r).sort((u,f)=>f[1]-u[1]).slice(0,5).map(([u,f])=>`${u} (${f})`),i={};for(let u of n)for(let f of u.techStack||[])i[f]=(i[f]||0)+1;let c=Object.entries(i).sort((u,f)=>f[1]-u[1]).slice(0,8).map(([u,f])=>`${u} (${f})`),a=re(),m=new Date().toISOString().slice(0,10),l=a.visits.filter(u=>u.date.startsWith(m)).length,y=new Set(a.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:c,totalVisited:y,todayVisited:l,lastScoutDate:n.length>0?n.sort((u,f)=>(f.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}le();async function Et(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:c=!1}=n,a={visited:0,skipped:0,errors:0},m=Date.now()-s*24*60*60*1e3;for(let l of t){if(a.visited>=o)break;if(!l.profileUrl)continue;let y=xt(l.profileUrl);if(y&&new Date(y).getTime()>m){a.skipped++;continue}try{let u=await St(e,l.profileUrl,{extractExperience:c}),f=l.source||"visit";if(u){let v={...u,source:f};c&&Array.isArray(u.experience)&&u.experience.length>0&&(v._lastEnrichedAt=new Date().toISOString()),_e(l.profileUrl,v)}Ct(l.profileUrl,{name:u?.name||l.name||null,source:f}),a.visited++,i?console.log(` [${a.visited}] ${u?.name||l.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):a.visited%10===0&&console.log(` Visited: ${a.visited}`);let C=Math.random()*4e3;await $(r+C)}catch(u){a.errors++,i&&console.error(` Error visiting ${l.name||l.profileUrl}: ${u.message?.slice(0,60)}`);let f=(u.message||"").toLowerCase();if(f.includes("detached")||f.includes("session closed")||f.includes("target closed")||f.includes("session expired")||f.includes("bounced to")||f.includes("authwall")||f.includes("checkpoint")||f.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return a}function At(e={}){let{limit:t=150,revisitDays:n=14}=e,o=re(),r=new Set(o.visits?.map(h=>h.profileUrl)||[]),s=new Map;for(let h of o.visits||[]){let g=s.get(h.profileUrl);(!g||h.date>g)&&s.set(h.profileUrl,h.date)}let i=Date.now()-7*24*60*60*1e3,c=Date.now()-n*24*60*60*1e3;function a(h){let g=s.get(h);return g&&new Date(g).getTime()>i}let m=we(),l=Object.entries(m).filter(([h])=>!r.has(h)).map(([h,g])=>({profileUrl:h,name:g.name,source:"new-discovered",priority:1,jobCount:g.jobIds?.length||0})),y=ae(),u=Object.entries(y).filter(([h,g])=>g.connectionStatus==="connected"||a(h)?!1:(g.jobCount||g.jobIds?.length||0)>=2).map(([h,g])=>({profileUrl:h,name:g.name,source:"active-recruiter",priority:2,jobCount:g.jobCount||g.jobIds?.length||0})),f=Object.entries(y).filter(([h,g])=>{let x=g.connectionStatus;return!(!x||x==="connected"||x==="pending"||x==="sent_without_note"||x==="sent_with_note"||a(h)||(g.jobCount||g.jobIds?.length||0)>=2)}).map(([h,g])=>({profileUrl:h,name:g.name,source:"unconnected",priority:3,jobCount:g.jobCount||g.jobIds?.length||0})),C=[...s.entries()].filter(([h,g])=>new Date(g).getTime()<c).map(([h,g])=>({profileUrl:h,name:null,source:"revisit",priority:4,lastVisit:g})),v=h=>h.sort(()=>Math.random()-.5);return l.sort((h,g)=>g.jobCount-h.jobCount),u.sort((h,g)=>g.jobCount-h.jobCount),v(f),v(C),[...l,...u,...f,...C].slice(0,t)}function _t(){let e=we();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function Te(){let e=ae();return Object.entries(e).filter(([,t])=>{let n=t.connectionStatus;return n&&n!=="connected"&&n!=="pending"&&n!=="sent_without_note"&&n!=="sent_with_note"}).map(([t,n])=>({profileUrl:t,name:n.name,source:"unconnected"}))}function De(e=14){let t=re(),n=Date.now()-e*24*60*60*1e3,o=new Map;for(let r of t.visits){let s=o.get(r.profileUrl);(!s||r.date>s.date)&&o.set(r.profileUrl,r)}return[...o.entries()].filter(([,r])=>new Date(r.date).getTime()<n).map(([r,s])=>({profileUrl:r,name:s.name,source:"revisit"}))}import"dotenv/config";import*as N from"fs";import*as J from"path";import{fileURLToPath as zn}from"url";import"dotenv/config";import{createGraphClient as Jn,resolveToken as Wn}from"@ilivemylife/graph-sdk";import{config as Yn}from"dotenv";Yn();var It=Wn()||process.env.TOKEN;It||(console.error(`
241
+ `),await e.close(),process.exit(1)}return{browser:e,page:n}}le();import"dotenv/config";import*as A from"fs";import*as E from"path";import{fileURLToPath as dn}from"url";var fn=dn(import.meta.url),mn=E.dirname(E.dirname(fn)),U=process.env.DATA_DIR?E.resolve(process.env.DATA_DIR,"collected-profiles"):E.join(mn,"collected-profiles"),$e=E.join(U,"people.json"),fr=E.join(U,"jobs.json"),mr=E.join(U,"quota.json"),pr=E.join(U,"conversations.json"),hr=E.join(U,"sync-state.json"),gr=E.join(U,"application-questions.json"),Ae=E.join(U,"profile-history"),ft=(e,t)=>{let n=parseInt(e,10);return Number.isInteger(n)&&n>=0?n:t},yr=ft(process.env.CONNECT_MAX_DAILY,20),wr=ft(process.env.CONNECT_MAX_WEEKLY,100),oe=null,pn=null,hn=null,gn=null,yn=null,wn=null;function mt(){oe=null,pn=null,hn=null,gn=null,yn=null,wn=null}function pt(){A.existsSync(U)||A.mkdirSync(U,{recursive:!0})}function ht(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";A.writeFileSync(o,n);for(let r=0;r<3;r++)try{A.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function gt(e,t){if(!A.existsSync(e))return null;try{let n=JSON.parse(A.readFileSync(e,"utf-8"));try{A.copyFileSync(e,e+".backup")}catch{}return n}catch(n){console.error(`[!] ${t} is corrupted: ${n.message}`),console.error(` File: ${E.resolve(e)}`);let o=e+".backup";if(A.existsSync(o))try{let r=JSON.parse(A.readFileSync(o,"utf-8"));console.error(` Restored from ${o}`);try{A.copyFileSync(o,e)}catch{}return r}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{A.copyFileSync(e,e+".corrupted")}catch{}return null}}function bn(){return new Date().toISOString().split("T")[0]}var Sn=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge","about","currentCompany","currentSchool","connectionsLevel","mutualConnectionsCount","followers","openToWork","hiring","topSkills","availableSections","experience","featuredLinks","_lastEnrichedAt","featured","education","skillsFull","recommendations","certifications"],vn=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"];function Ee(e){if(!e)return null;let t=e.match(/\/in\/([^/?#]+)/);return t?t[1].toLowerCase():null}function kn(){A.existsSync(Ae)||A.mkdirSync(Ae,{recursive:!0})}function yt(e){let t=Ee(e);return t?E.join(Ae,`${t}.json`):null}function Cn(e){let t=yt(e);return t?gt(t,`profile-history/${Ee(e)}.json`)||[]:[]}function xn(e,t){let n=yt(e);if(!n)return;kn();let o=Cn(e);o.unshift(t);try{ht(n,o)}catch(r){console.error(`[!] Failed to append profile-history for ${Ee(e)}: ${r.message}`)}}function ut(e){return!!(e==null||Array.isArray(e)&&e.length===0||typeof e=="string"&&!e.trim())}function dt(e){return Array.isArray(e)?[...e].map(t=>String(t).trim()).sort().join("||"):typeof e=="string"?e.replace(/\s+/g," ").trim():e}function $n(e,t){if(!e)return[];let n=[];for(let o of vn){let r=e[o],s=t[o];ut(r)||ut(s)||dt(r)!==dt(s)&&n.push(o)}return n}function An(e){let t=new Set(["seenCount","sources","firstSeen","lastSeen","lastUpdated","encounters","jobIds","lastFullVisitAt","lastSnapshotAt","messageSent","connectionStatus","connectionDate","connectionMethod"]),n={};for(let[o,r]of Object.entries(e))t.has(o)||(n[o]=r);return n}function ae(){return oe||(pt(),oe=gt($e,"people.json")||{},oe)}function En(e){pt(),oe=e;try{ht($e,e)}catch(t){console.error(`[!] Failed to save people.json: ${t.message}`),console.error(` Path: ${E.resolve($e)}`)}}function _e(e,t={}){if(!e)return null;let n=ae(),o=bn(),r=new Date().toISOString(),s=n[e];if(s){let i=$n(s,t);i.length>0&&(xn(e,{capturedAt:r,previousLastUpdated:s.lastUpdated||null,changedFields:i,snapshot:An(s)}),s.lastSnapshotAt=r),s.seenCount=(s.seenCount||1)+1,s.lastSeen=o,s.lastUpdated=r,t.source&&!s.sources?.includes(t.source)&&(s.sources=s.sources||[],s.sources.push(t.source));for(let c of Sn)if(t[c]!=null){if(Array.isArray(t[c])&&t[c].length===0&&Array.isArray(s[c])&&s[c].length>0)continue;s[c]=t[c]}Array.isArray(t.availableSections)&&(s.lastFullVisitAt=r)}else n[e]={name:t.name||null,title:t.title||null,location:t.location||null,company:t.company||null,about:t.about||null,connectionDegree:t.connectionDegree||null,connectionsCount:t.connectionsCount||null,mutualConnections:t.mutualConnections||null,photoUrl:t.photoUrl||null,hasVerifiedBadge:t.hasVerifiedBadge||!1,currentCompany:t.currentCompany||null,currentSchool:t.currentSchool||null,connectionsLevel:t.connectionsLevel||null,mutualConnectionsCount:t.mutualConnectionsCount||null,followers:t.followers||null,openToWork:t.openToWork||!1,hiring:t.hiring||!1,topSkills:t.topSkills||null,availableSections:t.availableSections||null,experience:t.experience||null,featuredLinks:t.featuredLinks||null,_lastEnrichedAt:t._lastEnrichedAt||null,featured:t.featured||null,education:t.education||null,skillsFull:t.skillsFull||null,recommendations:t.recommendations||null,certifications:t.certifications||null,firstSeen:o,lastSeen:o,seenCount:1,sources:t.source?[t.source]:[],connectionStatus:null,requestedConnectionAt:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:r,lastSnapshotAt:null,lastFullVisitAt:Array.isArray(t.availableSections)?r:null};return En(n),n[e]}async function On(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: npm run login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await $(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function St(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:c=!1,extractCertifications:a=!1}=n;try{await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await $(3e3),await st(e)&&(console.log(" Dismissed Premium overlay"),await $(1500),await On(e,t));let l=0;for(let x=0;x<2;x++){let te=await e.evaluate(()=>document.body.scrollHeight);for(let Y=1;Y<=6;Y++)await e.evaluate(T=>window.scrollTo(0,T),te*Y/6),await $(900);let B=await e.evaluate(()=>document.body.scrollHeight);if(B===l)break;l=B}await $(800);let y=await e.evaluate(()=>{let x=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),te=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),B=p=>{let w=(p||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(w)},Y={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},T=document.querySelector("main");if(!T)return Y;let ke=[...Array.from(T.querySelectorAll("h1")),...Array.from(T.querySelectorAll("h2"))],b=Array.from(T.querySelectorAll("h2")),I=ke.find(p=>{let w=p.textContent&&p.textContent.trim(),S=(w||"").replace(/\s*\(\d+\)$/,"").trim();return w&&w.length>1&&w.length<100&&!x.has(S)&&!te.has(S)}),ne=I?I.textContent.trim():null,D=I?I.parentElement:null;for(;D&&D!==T&&!Array.from(D.children).filter(w=>w.tagName==="P").some(w=>{let S=w.textContent&&w.textContent.trim();return S&&S.length>5&&!B(S)});)D=D.parentElement;let he=null,Je=null,We=null,Ce=null;if(D&&D!==T){let p=Array.from(D.children).filter(S=>S.tagName==="P").filter(S=>{let H=S.textContent&&S.textContent.trim();return H&&H.length>5&&!B(H)});if(p[0]&&(he=p[0].textContent.trim()),p[1]){let S=p[1].textContent.trim().split(/\s*[·•]\s*/);Je=S[0]||null,S.length>1&&(We=S.slice(1).join(" \xB7 "))}let w=Array.from(D.children).filter(S=>S.tagName==="DIV");for(let S=w.length-1;S>=0;S--){let H=w[S].textContent&&w[S].textContent.trim();if(H&&!(ne&&H.startsWith(ne))&&(Ce=H.split(/[·•]/)[0].trim(),Ce))break}}let Ye=null;if(D){let p=D.querySelectorAll("p, span, div");for(let w of p){let S=(w.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){Ye=S;break}}}let Wt=!!T.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),Ge=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Yt=Ge&&Ge.src||null,ge=T.textContent||"",Ke=null,ze=ge.match(/(\d+\+?)\s+connections\b/i);ze&&(Ke=ze[1]+" connections");let ye=null,Qe=ge.match(/(\d+)\s+mutual connection/i),Ze=ge.match(/and\s+(\d+)\s+other mutual/i),Gt=ge.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);Qe?ye=parseInt(Qe[1],10):Ze?ye=2+parseInt(Ze[1],10):Gt&&(ye=2);let Xe=null,et=null,tt=b.find(p=>p.textContent&&p.textContent.trim()==="About");if(tt){let p=tt.parentElement;for(;p&&p.tagName!=="SECTION";)p=p.parentElement;if(p){let w=(p.textContent||"").trim();w=w.replace(/^About\s*/,"").trim();let S=w.match(/Top skills\s*(.+?)$/i);if(S){let xe=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Zt=>Zt.trim()).filter(Boolean);xe.length>0&&xe.length<10&&(et=xe)}w=w.split(/\n?…\s*more/i)[0].trim(),w=w.split(/\n?\.\.\.\s*more/i)[0].trim(),w=w.split(/\bTop skills\b/i)[0].trim(),w=w.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),Xe=w||null}}let nt=null,ot=b.find(p=>p.textContent&&p.textContent.trim()==="Activity");if(ot){let p=ot.parentElement;for(;p&&p.tagName!=="SECTION";)p=p.parentElement;if(p){let w=(p.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);w&&(nt=parseInt(w[1].replace(/,/g,""),10))}}let Kt=[...new Set(b.map(p=>(p.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(p=>p&&p.length>1&&p.length<60&&p!==ne).filter(p=>x.has(p)))],se=document.body.textContent||"",zt=/#OpenToWork\b/i.test(se)||/\bOpen to work\b/.test(se),Qt=/#Hiring\b/i.test(se)||/\bI'?m hiring\b/i.test(se)||/\bWe are hiring\b/i.test(se);return{name:ne,title:he,location:Ce,currentCompany:Je,currentSchool:We,connectionDegree:Ye,hasVerifiedBadge:Wt,photoUrl:Yt,connectionsLevel:Ke,mutualConnectionsCount:ye,followers:nt,openToWork:zt,hiring:Qt,about:Xe,topSkills:et,availableSections:Kt}});console.log(`Viewed: ${y.name} - ${y.title}`);let u=null,f=null,C=null,v=null,_=null,h=null;if(o||r||s||i||c||a){let x=await Promise.resolve().then(()=>(bt(),wt));o&&(u=await x.extractExperienceFromDetails(e,t),console.log(` + Experience: ${u.length} entries`)),r&&(f=await x.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${f.length} entries`)),s&&(C=await x.extractEducationFromDetails(e,t),console.log(` + Education: ${C.length} entries`)),i&&(v=await x.extractSkillsFromDetails(e,t),console.log(` + Skills: ${v.length} entries`)),c&&(_=await x.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${_.length} entries`)),a&&(h=await x.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${h.length} entries`))}return{...y,profileUrl:t,...u!==null&&{experience:u},...f!==null&&{featured:f},...C!==null&&{education:C},...v!==null&&{skillsFull:v},..._!==null&&{recommendations:_},...h!==null&&{certifications:h}}}catch(m){console.error(`Error viewing profile ${t}:`,m.message);try{await e.goto("about:blank",{timeout:1e4}),await $(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(m.message||""))throw m;return null}}import"dotenv/config";import*as O from"fs";import*as j from"path";import{fileURLToPath as Nn}from"url";var Fn=Nn(import.meta.url),Ln=j.dirname(j.dirname(Fn)),z=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(Ln,"market-research"),Mn=j.join(z,"scouted-jobs.json"),Pn=j.join(z,"discovered-people.json"),vt=j.join(z,"visit-log.json"),Rr=j.join(z,"companies.json"),jr=j.join(z,"job-decisions.json");function qn(){O.existsSync(z)||O.mkdirSync(z,{recursive:!0})}function Bn(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";O.writeFileSync(o,n);for(let r=0;r<3;r++)try{O.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function Ie(e){try{if(O.existsSync(e))return JSON.parse(O.readFileSync(e,"utf-8"))}catch{}return null}function kt(){fe=null,Vn=null,me=null,Hn=null,K=null}var fe=null;function Un(){return fe||(fe=Ie(Mn)||{},fe)}var Vn=null;var me=null;function we(){return me||(me=Ie(Pn)||{},me)}var Hn=null;var K=null;function re(){return K||(K=Ie(vt)||{visits:[]},Array.isArray(K.visits)||(K.visits=[]),K)}function Ct(e,t={}){if(!e)return;qn();let n=re();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{Bn(vt,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function xt(e){let t=re();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function $t(){let e=Un(),t=we(),n=Object.values(e),o=Object.values(t),r={};for(let u of n)u.company&&(r[u.company]=(r[u.company]||0)+1);let s=Object.entries(r).sort((u,f)=>f[1]-u[1]).slice(0,5).map(([u,f])=>`${u} (${f})`),i={};for(let u of n)for(let f of u.techStack||[])i[f]=(i[f]||0)+1;let c=Object.entries(i).sort((u,f)=>f[1]-u[1]).slice(0,8).map(([u,f])=>`${u} (${f})`),a=re(),m=new Date().toISOString().slice(0,10),l=a.visits.filter(u=>u.date.startsWith(m)).length,y=new Set(a.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:c,totalVisited:y,todayVisited:l,lastScoutDate:n.length>0?n.sort((u,f)=>(f.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}le();async function At(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:c=!1}=n,a={visited:0,skipped:0,errors:0},m=Date.now()-s*24*60*60*1e3;for(let l of t){if(a.visited>=o)break;if(!l.profileUrl)continue;let y=xt(l.profileUrl);if(y&&new Date(y).getTime()>m){a.skipped++;continue}try{let u=await St(e,l.profileUrl,{extractExperience:c}),f=l.source||"visit";if(u){let v={...u,source:f};c&&Array.isArray(u.experience)&&u.experience.length>0&&(v._lastEnrichedAt=new Date().toISOString()),_e(l.profileUrl,v)}Ct(l.profileUrl,{name:u?.name||l.name||null,source:f}),a.visited++,i?console.log(` [${a.visited}] ${u?.name||l.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):a.visited%10===0&&console.log(` Visited: ${a.visited}`);let C=Math.random()*4e3;await $(r+C)}catch(u){a.errors++,i&&console.error(` Error visiting ${l.name||l.profileUrl}: ${u.message?.slice(0,60)}`);let f=(u.message||"").toLowerCase();if(f.includes("detached")||f.includes("session closed")||f.includes("target closed")||f.includes("session expired")||f.includes("bounced to")||f.includes("authwall")||f.includes("checkpoint")||f.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return a}function Et(e={}){let{limit:t=150,revisitDays:n=14}=e,o=re(),r=new Set(o.visits?.map(h=>h.profileUrl)||[]),s=new Map;for(let h of o.visits||[]){let g=s.get(h.profileUrl);(!g||h.date>g)&&s.set(h.profileUrl,h.date)}let i=Date.now()-7*24*60*60*1e3,c=Date.now()-n*24*60*60*1e3;function a(h){let g=s.get(h);return g&&new Date(g).getTime()>i}let m=we(),l=Object.entries(m).filter(([h])=>!r.has(h)).map(([h,g])=>({profileUrl:h,name:g.name,source:"new-discovered",priority:1,jobCount:g.jobIds?.length||0})),y=ae(),u=Object.entries(y).filter(([h,g])=>g.connectionStatus==="connected"||a(h)?!1:(g.jobCount||g.jobIds?.length||0)>=2).map(([h,g])=>({profileUrl:h,name:g.name,source:"active-recruiter",priority:2,jobCount:g.jobCount||g.jobIds?.length||0})),f=Object.entries(y).filter(([h,g])=>{let x=g.connectionStatus;return!(!x||x==="connected"||x==="pending"||x==="sent_without_note"||x==="sent_with_note"||a(h)||(g.jobCount||g.jobIds?.length||0)>=2)}).map(([h,g])=>({profileUrl:h,name:g.name,source:"unconnected",priority:3,jobCount:g.jobCount||g.jobIds?.length||0})),C=[...s.entries()].filter(([h,g])=>new Date(g).getTime()<c).map(([h,g])=>({profileUrl:h,name:null,source:"revisit",priority:4,lastVisit:g})),v=h=>h.sort(()=>Math.random()-.5);return l.sort((h,g)=>g.jobCount-h.jobCount),u.sort((h,g)=>g.jobCount-h.jobCount),v(f),v(C),[...l,...u,...f,...C].slice(0,t)}function _t(){let e=we();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function Te(){let e=ae();return Object.entries(e).filter(([,t])=>{let n=t.connectionStatus;return n&&n!=="connected"&&n!=="pending"&&n!=="sent_without_note"&&n!=="sent_with_note"}).map(([t,n])=>({profileUrl:t,name:n.name,source:"unconnected"}))}function De(e=14){let t=re(),n=Date.now()-e*24*60*60*1e3,o=new Map;for(let r of t.visits){let s=o.get(r.profileUrl);(!s||r.date>s.date)&&o.set(r.profileUrl,r)}return[...o.entries()].filter(([,r])=>new Date(r.date).getTime()<n).map(([r,s])=>({profileUrl:r,name:s.name,source:"revisit"}))}import"dotenv/config";import*as N from"fs";import*as J from"path";import{fileURLToPath as zn}from"url";import"dotenv/config";import{createGraphClient as Jn,resolveToken as Wn}from"@ilivemylife/graph-sdk";import{config as Yn}from"dotenv";Yn();var It=Wn()||process.env.TOKEN;It||(console.error(`
242
242
  [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.
243
243
  `),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
244
244
  `),console.error(` Or add ILML_TOKEN=... to .env
245
245
  `),process.exit(1));var Tt=Jn({token:It});function Gn(e){if(!e||e<1e3)return`${e||0}ms`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60),o=t%60;return o?`${n}m ${o}s`:`${n}m`}function Kn({scriptName:e,mode:t,status:n,error:o,duration:r}){let s=n==="error"?"\u2717":n==="quota-reached"?"\u26A0":"\u2713",i=new Date().toISOString().slice(0,16).replace("T"," "),c=r?` ${Gn(r)}`:"",a=n==="error"?`error${o?`: ${String(o).slice(0,80)}`:""}`:n==="quota-reached"?"daily quota reached":"done",m=t&&t!=="default"?` (${t})`:"";return`${s} ${e}${m} \u2014 ${a} \u2014 ${i}${c}`}async function Dt({scriptName:e,mode:t,status:n,summaryLines:o,error:r,duration:s}){let i=process.env.NODE_RUN_REPORTS;if(!i||!Array.isArray(o)||o.length===0)return;let a=`**${Kn({scriptName:e,mode:t,status:n,error:r,duration:s})}**
246
246
 
247
247
  ${o.join(`
248
- `)}`;try{await Tt.addMessage(i,a)}catch(m){console.error(`[runReport] Failed to post end-of-session message: ${m.message}`)}}var Qn=zn(import.meta.url),Zn=J.dirname(J.dirname(Qn)),Re=process.env.DATA_DIR?J.resolve(process.env.DATA_DIR,"collected-profiles"):J.join(Zn,"collected-profiles"),je=J.join(Re,"run-log.json");function Xn(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function Rt(){try{if(!N.existsSync(je))return{runs:[]};let e=JSON.parse(N.readFileSync(je,"utf-8"));return!e.runs||!Array.isArray(e.runs)?{runs:[]}:e}catch{return{runs:[]}}}function jt(e){N.existsSync(Re)||N.mkdirSync(Re,{recursive:!0});try{Xn(je,e)}catch(t){console.error(`[!] Failed to save run-log.json: ${t.message}`)}}function Ot(e,t="default"){let n=Rt();for(let i of n.runs)i.status==="running"&&(i.status="interrupted",i.result="interrupted",i.timestamp&&(i.duration=Date.now()-new Date(i.timestamp).getTime()));let o=new Date,r={timestamp:o.toISOString(),script:e,mode:t,status:"running",result:null,duration:null,stats:null,error:null};n.runs.push(r);let s=Date.now()-30*24*60*60*1e3;return n.runs=n.runs.filter(i=>new Date(i.timestamp).getTime()>s),jt(n),{script:e,mode:t,startTime:o.getTime(),runIndex:n.runs.length-1}}function Oe(e,t,n=null,o=null){if(!e)return;let r=null;try{let i=Rt(),c=i.runs[e.runIndex];if(c&&c.status==="running"&&c.script===e.script)c.status=t,c.result=t,c.duration=Date.now()-e.startTime,c.stats=n,c.error=o,r=c.duration;else{let a=i.runs.find(m=>m.script===e.script&&m.status==="running"&&m.timestamp===new Date(e.startTime).toISOString());a&&(a.status=t,a.result=t,a.duration=Date.now()-e.startTime,a.stats=n,a.error=o,r=a.duration)}jt(i)}catch(i){console.error(`[runLog] Failed to end run: ${i.message}`)}let s=n&&Array.isArray(n.summaryLines)?n.summaryLines:null;if(s&&s.length>0&&process.env.NODE_RUN_REPORTS){let i=t==="error"?"error":n&&n.dailyLimitHit?"quota-reached":"done";Dt({scriptName:e.script,mode:e.mode,status:i,summaryLines:s,error:o,duration:r??Date.now()-e.startTime}).catch(c=>console.error(`[runLog] postRunReport failed: ${c.message}`))}}import"dotenv/config";import*as d from"fs";import*as Ue from"os";import*as k from"path";import{fileURLToPath as xo}from"url";var Le={};ie(Le,{NAME:()=>to,VERSION:()=>eo,up:()=>ro});import*as F from"fs";import*as be from"path";var eo=1,to="normalize-jobs-and-questions",no=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],oo=50;function Ne(e){return F.existsSync(e)?JSON.parse(F.readFileSync(e,"utf-8")):null}function Fe(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{F.writeFileSync(o,n)}catch(r){try{F.unlinkSync(o)}catch{}throw r}try{F.renameSync(o,e)}catch(r){try{F.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function ro(e,t){let n=be.join(e,"jobs.json"),o=be.join(t,"scouted-jobs.json"),r=be.join(e,"application-questions.json"),s=Ne(n)||{},i=Ne(o)||{},c=0,a=[];for(let l of Object.keys(s)){if(!i[l])continue;let y=i[l],u=s[l];for(let f of no)y[f]!=null&&u[f]==null&&(u[f]=y[f]);a.push(l),c++}c>0?(Fe(n,s),console.log(` Phase 1: Merged ${c} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let m=Ne(r);if(m&&Array.isArray(m.applications)){let l={},y=0;for(let u of m.applications){let f=u.jobId||"unknown";l[f]||(l[f]={applications:[]});let C=(u.questions||[]).map(v=>{let _={type:v.type||"input",question:v.question||"",answer:v.answer||""};return v.options&&(v.options.length<=oo?_.options=v.options:(_.optionsOmitted=!0,y++)),v.page&&(_.page=v.page),v.wasPreFilled&&(_.wasPreFilled=!0),v.wasRetry&&(_.wasRetry=!0),_});l[f].applications.push({applicationId:u.applicationId,date:u.date,result:u.result||"submitted",formPages:u.formPages||null,duration:u.duration||null,retries:u.retries||0,failedField:u.failedField||null,questionCount:u.questionCount||C.length,questions:C})}Fe(r,l),console.log(` Phase 2: Rekeyed ${m.applications.length} applications by jobId (${Object.keys(l).length} unique jobs)`),y>0&&console.log(` Phase 2: Stripped bloated options from ${y} questions`)}else m&&!m.applications?console.log(" Phase 2: application-questions.json already in keyed format"):console.log(" Phase 2: No application-questions.json found");if(a.length>0){for(let l of a)delete i[l];Fe(o,i),console.log(` Phase 3: Removed ${a.length} merged entries from scouted-jobs.json (${Object.keys(i).length} remaining)`)}else console.log(" Phase 3: No entries to remove from scouted-jobs.json")}var Me={};ie(Me,{NAME:()=>io,VERSION:()=>so,up:()=>co});import*as L from"fs";import*as Nt from"path";var so=2,io="reset-unread-flags";function lo(e){return L.existsSync(e)?JSON.parse(L.readFileSync(e,"utf-8")):null}function ao(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{L.writeFileSync(o,n)}catch(r){try{L.unlinkSync(o)}catch{}throw r}try{L.renameSync(o,e)}catch(r){try{L.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function co(e){let t=Nt.join(e,"conversations.json"),n=lo(t);if(!n){console.log(" conversations.json not found \u2014 nothing to reset.");return}let o=0;for(let r of Object.keys(n))n[r].unread===!0&&(n[r].unread=!1,o++);if(o===0){console.log(" No stale unread flags found \u2014 nothing to do.");return}ao(t,n),console.log(` Reset unread=false on ${o} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var Pe={};ie(Pe,{NAME:()=>fo,VERSION:()=>uo,up:()=>yo});import*as M from"fs";import*as Ft from"path";var uo=3,fo="recompute-last-message-by",mo=new Set(["draft","confirmed","rejected","needs_review"]);function po(e){return M.existsSync(e)?JSON.parse(M.readFileSync(e,"utf-8")):null}function ho(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{M.writeFileSync(o,n)}catch(r){try{M.unlinkSync(o)}catch{}throw r}try{M.renameSync(o,e)}catch(r){try{M.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}function go(e,t){if(!e||e.length===0)return"them";let n=e.filter(s=>!mo.has(s.status));if(n.length===0)return"them";let o=n.map(s=>({m:s,t:Date.parse(s.time)})).filter(s=>Number.isFinite(s.t)).sort((s,i)=>i.t-s.t).map(s=>s.m),r=o.length>0?o:[...n].reverse();for(let s of r){let i=s.sender;if(i===t)return"me";if(i!=="Unknown"&&i!=="")return"them"}return"them"}async function yo(e){let t=Ft.join(e,"conversations.json"),n=po(t);if(!n){console.log(" conversations.json not found \u2014 nothing to recompute.");return}let o=process.env.LINKEDIN_NAME;if(!o||typeof o!="string"||!o.trim())throw new Error(`LINKEDIN_NAME env var is not set. Migration v003 needs your display name to determine which messages are yours vs theirs. Set LINKEDIN_NAME in your .env (or run "ilml plugin config linkedin set LINKEDIN_NAME 'Your Name'") and retry.`);let r=0,s=0,i=0;for(let c of Object.keys(n)){let a=n[c];if(!a||typeof a!="object")continue;Object.prototype.hasOwnProperty.call(a,"_cardSynced")&&(delete a._cardSynced,s++);let m=Array.isArray(a.messages)?a.messages:[],l=go(m,o),y=l==="me"?"awaiting_reply":"unanswered_by_us";a.lastMessageBy!==l&&i++,a.lastMessageBy=l,a.conversationStatus=y,r++}if(r===0){console.log(" No conversations to recompute.");return}ho(t,n),console.log(` Recomputed lastMessageBy + conversationStatus on ${r} conversations.`),console.log(` ${i} had stale lastMessageBy (now corrected).`),s>0&&console.log(` Cleaned ${s} leftover _cardSynced flags.`),console.log(" Re-run 'ilml linkedin enrich' if you want actionNeeded / summary / autoTags to also update.")}var qe={};ie(qe,{NAME:()=>bo,VERSION:()=>wo,up:()=>Co});import*as P from"fs";import*as Lt from"path";var wo=4,bo="split-connection-timestamps",So=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function vo(e){if(!P.existsSync(e))return null;let t=P.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function ko(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{P.writeFileSync(o,n)}catch(r){try{P.unlinkSync(o)}catch{}throw r}try{P.renameSync(o,e)}catch(r){try{P.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function Co(e){let t=Lt.join(e,"people.json"),n=vo(t);if(!n||typeof n!="object"){console.log(" people.json not found \u2014 nothing to migrate.");return}let o=0,r=0;for(let s of Object.keys(n)){let i=n[s];!i||typeof i!="object"||(r++,So.has(i.connectionStatus)&&i.connectionDate&&!i.requestedConnectionAt&&(i.requestedConnectionAt=i.connectionDate,i.connectionDate=null,o++))}if(o===0){console.log(` Scanned ${r} people \u2014 none needed the connectionDate\u2192requestedConnectionAt move.`);return}ko(t,n),console.log(` Moved connectionDate \u2192 requestedConnectionAt on ${o} still-pending people (of ${r} scanned).`),console.log(' connectionDate now means "accepted on" only; requestedConnectionAt means "requested on".')}var $o=xo(import.meta.url),Eo=k.dirname($o),Ao=k.dirname(k.dirname(Eo));function _o(e){return e&&(e==="~"?Ue.homedir():e.startsWith("~/")||e.startsWith("~\\")?k.join(Ue.homedir(),e.slice(2)):e)}var q=process.env.DATA_DIR?k.resolve(_o(process.env.DATA_DIR)):Ao,Z=k.join(q,"collected-profiles"),X=k.join(q,"market-research"),R=k.join(q,".schema-version"),Mt=[k.join(Z,".schema-version"),k.join(X,".schema-version")],W=4,V=[{version:1,module:Le},{version:2,module:Me},{version:3,module:Pe},{version:4,module:qe}];(function(){V.sort((o,r)=>o.version-r.version);let t=new Set;for(let o of V){if(!Number.isInteger(o.version)||o.version<1)throw new Error(`MIGRATION_REGISTRY: invalid version ${JSON.stringify(o.version)} (must be positive integer)`);if(t.has(o.version))throw new Error(`MIGRATION_REGISTRY: duplicate version v${o.version}`);t.add(o.version);let r=o.module;if(!r||typeof r.up!="function")throw new Error(`MIGRATION_REGISTRY: v${o.version} module is missing up()`);if(r.VERSION!==void 0&&r.VERSION!==o.version)throw new Error(`MIGRATION_REGISTRY: v${o.version} entry does not match module VERSION (${r.VERSION}). Drift between registry and module \u2014 pick one source of truth.`)}let n=V.length?V[V.length-1].version:0;if(n!==W)throw new Error(`MIGRATION_REGISTRY: highest version v${n} but CURRENT_SCHEMA_VERSION=${W}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let o=0;o<V.length;o++){let r=o+1;if(V[o].version!==r)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${r} at position ${o} but found v${V[o].version}`)}})();function Ut(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
248
+ `)}`;try{await Tt.addMessage(i,a)}catch(m){console.error(`[runReport] Failed to post end-of-session message: ${m.message}`)}}var Qn=zn(import.meta.url),Zn=J.dirname(J.dirname(Qn)),Re=process.env.DATA_DIR?J.resolve(process.env.DATA_DIR,"collected-profiles"):J.join(Zn,"collected-profiles"),je=J.join(Re,"run-log.json");function Xn(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function Rt(){try{if(!N.existsSync(je))return{runs:[]};let e=JSON.parse(N.readFileSync(je,"utf-8"));return!e.runs||!Array.isArray(e.runs)?{runs:[]}:e}catch{return{runs:[]}}}function jt(e){N.existsSync(Re)||N.mkdirSync(Re,{recursive:!0});try{Xn(je,e)}catch(t){console.error(`[!] Failed to save run-log.json: ${t.message}`)}}function Ot(e,t="default"){let n=Rt();for(let i of n.runs)i.status==="running"&&(i.status="interrupted",i.result="interrupted",i.timestamp&&(i.duration=Date.now()-new Date(i.timestamp).getTime()));let o=new Date,r={timestamp:o.toISOString(),script:e,mode:t,status:"running",result:null,duration:null,stats:null,error:null};n.runs.push(r);let s=Date.now()-30*24*60*60*1e3;return n.runs=n.runs.filter(i=>new Date(i.timestamp).getTime()>s),jt(n),{script:e,mode:t,startTime:o.getTime(),runIndex:n.runs.length-1}}function Oe(e,t,n=null,o=null){if(!e)return;let r=null;try{let i=Rt(),c=i.runs[e.runIndex];if(c&&c.status==="running"&&c.script===e.script)c.status=t,c.result=t,c.duration=Date.now()-e.startTime,c.stats=n,c.error=o,r=c.duration;else{let a=i.runs.find(m=>m.script===e.script&&m.status==="running"&&m.timestamp===new Date(e.startTime).toISOString());a&&(a.status=t,a.result=t,a.duration=Date.now()-e.startTime,a.stats=n,a.error=o,r=a.duration)}jt(i)}catch(i){console.error(`[runLog] Failed to end run: ${i.message}`)}let s=n&&Array.isArray(n.summaryLines)?n.summaryLines:null;if(s&&s.length>0&&process.env.NODE_RUN_REPORTS){let i=t==="error"?"error":n&&n.dailyLimitHit?"quota-reached":"done";Dt({scriptName:e.script,mode:e.mode,status:i,summaryLines:s,error:o,duration:r??Date.now()-e.startTime}).catch(c=>console.error(`[runLog] postRunReport failed: ${c.message}`))}}import"dotenv/config";import*as d from"fs";import*as Ue from"os";import*as k from"path";import{fileURLToPath as xo}from"url";var Le={};ie(Le,{NAME:()=>to,VERSION:()=>eo,up:()=>ro});import*as F from"fs";import*as be from"path";var eo=1,to="normalize-jobs-and-questions",no=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],oo=50;function Ne(e){return F.existsSync(e)?JSON.parse(F.readFileSync(e,"utf-8")):null}function Fe(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{F.writeFileSync(o,n)}catch(r){try{F.unlinkSync(o)}catch{}throw r}try{F.renameSync(o,e)}catch(r){try{F.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function ro(e,t){let n=be.join(e,"jobs.json"),o=be.join(t,"scouted-jobs.json"),r=be.join(e,"application-questions.json"),s=Ne(n)||{},i=Ne(o)||{},c=0,a=[];for(let l of Object.keys(s)){if(!i[l])continue;let y=i[l],u=s[l];for(let f of no)y[f]!=null&&u[f]==null&&(u[f]=y[f]);a.push(l),c++}c>0?(Fe(n,s),console.log(` Phase 1: Merged ${c} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let m=Ne(r);if(m&&Array.isArray(m.applications)){let l={},y=0;for(let u of m.applications){let f=u.jobId||"unknown";l[f]||(l[f]={applications:[]});let C=(u.questions||[]).map(v=>{let _={type:v.type||"input",question:v.question||"",answer:v.answer||""};return v.options&&(v.options.length<=oo?_.options=v.options:(_.optionsOmitted=!0,y++)),v.page&&(_.page=v.page),v.wasPreFilled&&(_.wasPreFilled=!0),v.wasRetry&&(_.wasRetry=!0),_});l[f].applications.push({applicationId:u.applicationId,date:u.date,result:u.result||"submitted",formPages:u.formPages||null,duration:u.duration||null,retries:u.retries||0,failedField:u.failedField||null,questionCount:u.questionCount||C.length,questions:C})}Fe(r,l),console.log(` Phase 2: Rekeyed ${m.applications.length} applications by jobId (${Object.keys(l).length} unique jobs)`),y>0&&console.log(` Phase 2: Stripped bloated options from ${y} questions`)}else m&&!m.applications?console.log(" Phase 2: application-questions.json already in keyed format"):console.log(" Phase 2: No application-questions.json found");if(a.length>0){for(let l of a)delete i[l];Fe(o,i),console.log(` Phase 3: Removed ${a.length} merged entries from scouted-jobs.json (${Object.keys(i).length} remaining)`)}else console.log(" Phase 3: No entries to remove from scouted-jobs.json")}var Me={};ie(Me,{NAME:()=>io,VERSION:()=>so,up:()=>co});import*as L from"fs";import*as Nt from"path";var so=2,io="reset-unread-flags";function lo(e){return L.existsSync(e)?JSON.parse(L.readFileSync(e,"utf-8")):null}function ao(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{L.writeFileSync(o,n)}catch(r){try{L.unlinkSync(o)}catch{}throw r}try{L.renameSync(o,e)}catch(r){try{L.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function co(e){let t=Nt.join(e,"conversations.json"),n=lo(t);if(!n){console.log(" conversations.json not found \u2014 nothing to reset.");return}let o=0;for(let r of Object.keys(n))n[r].unread===!0&&(n[r].unread=!1,o++);if(o===0){console.log(" No stale unread flags found \u2014 nothing to do.");return}ao(t,n),console.log(` Reset unread=false on ${o} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var Pe={};ie(Pe,{NAME:()=>fo,VERSION:()=>uo,up:()=>yo});import*as M from"fs";import*as Ft from"path";var uo=3,fo="recompute-last-message-by",mo=new Set(["draft","confirmed","rejected","needs_review"]);function po(e){return M.existsSync(e)?JSON.parse(M.readFileSync(e,"utf-8")):null}function ho(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{M.writeFileSync(o,n)}catch(r){try{M.unlinkSync(o)}catch{}throw r}try{M.renameSync(o,e)}catch(r){try{M.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}function go(e,t){if(!e||e.length===0)return"them";let n=e.filter(s=>!mo.has(s.status));if(n.length===0)return"them";let o=n.map(s=>({m:s,t:Date.parse(s.time)})).filter(s=>Number.isFinite(s.t)).sort((s,i)=>i.t-s.t).map(s=>s.m),r=o.length>0?o:[...n].reverse();for(let s of r){let i=s.sender;if(i===t)return"me";if(i!=="Unknown"&&i!=="")return"them"}return"them"}async function yo(e){let t=Ft.join(e,"conversations.json"),n=po(t);if(!n){console.log(" conversations.json not found \u2014 nothing to recompute.");return}let o=process.env.LINKEDIN_NAME;if(!o||typeof o!="string"||!o.trim())throw new Error(`LINKEDIN_NAME env var is not set. Migration v003 needs your display name to determine which messages are yours vs theirs. Set LINKEDIN_NAME in your .env (or run "ilml plugin config linkedin set LINKEDIN_NAME 'Your Name'") and retry.`);let r=0,s=0,i=0;for(let c of Object.keys(n)){let a=n[c];if(!a||typeof a!="object")continue;Object.prototype.hasOwnProperty.call(a,"_cardSynced")&&(delete a._cardSynced,s++);let m=Array.isArray(a.messages)?a.messages:[],l=go(m,o),y=l==="me"?"awaiting_reply":"unanswered_by_us";a.lastMessageBy!==l&&i++,a.lastMessageBy=l,a.conversationStatus=y,r++}if(r===0){console.log(" No conversations to recompute.");return}ho(t,n),console.log(` Recomputed lastMessageBy + conversationStatus on ${r} conversations.`),console.log(` ${i} had stale lastMessageBy (now corrected).`),s>0&&console.log(` Cleaned ${s} leftover _cardSynced flags.`),console.log(" Re-run 'ilml linkedin enrich' if you want actionNeeded / summary / autoTags to also update.")}var qe={};ie(qe,{NAME:()=>bo,VERSION:()=>wo,up:()=>Co});import*as P from"fs";import*as Lt from"path";var wo=4,bo="split-connection-timestamps",So=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function vo(e){if(!P.existsSync(e))return null;let t=P.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function ko(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{P.writeFileSync(o,n)}catch(r){try{P.unlinkSync(o)}catch{}throw r}try{P.renameSync(o,e)}catch(r){try{P.unlinkSync(o)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let s=new Error(`File locked: ${e} (${r.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw s.code=r.code,s}throw r}}async function Co(e){let t=Lt.join(e,"people.json"),n=vo(t);if(!n||typeof n!="object"){console.log(" people.json not found \u2014 nothing to migrate.");return}let o=0,r=0;for(let s of Object.keys(n)){let i=n[s];!i||typeof i!="object"||(r++,So.has(i.connectionStatus)&&i.connectionDate&&!i.requestedConnectionAt&&(i.requestedConnectionAt=i.connectionDate,i.connectionDate=null,o++))}if(o===0){console.log(` Scanned ${r} people \u2014 none needed the connectionDate\u2192requestedConnectionAt move.`);return}ko(t,n),console.log(` Moved connectionDate \u2192 requestedConnectionAt on ${o} still-pending people (of ${r} scanned).`),console.log(' connectionDate now means "accepted on" only; requestedConnectionAt means "requested on".')}var $o=xo(import.meta.url),Ao=k.dirname($o),Eo=k.dirname(k.dirname(Ao));function _o(e){return e&&(e==="~"?Ue.homedir():e.startsWith("~/")||e.startsWith("~\\")?k.join(Ue.homedir(),e.slice(2)):e)}var q=process.env.DATA_DIR?k.resolve(_o(process.env.DATA_DIR)):Eo,Z=k.join(q,"collected-profiles"),X=k.join(q,"market-research"),R=k.join(q,".schema-version"),Mt=[k.join(Z,".schema-version"),k.join(X,".schema-version")],W=4,V=[{version:1,module:Le},{version:2,module:Me},{version:3,module:Pe},{version:4,module:qe}];(function(){V.sort((o,r)=>o.version-r.version);let t=new Set;for(let o of V){if(!Number.isInteger(o.version)||o.version<1)throw new Error(`MIGRATION_REGISTRY: invalid version ${JSON.stringify(o.version)} (must be positive integer)`);if(t.has(o.version))throw new Error(`MIGRATION_REGISTRY: duplicate version v${o.version}`);t.add(o.version);let r=o.module;if(!r||typeof r.up!="function")throw new Error(`MIGRATION_REGISTRY: v${o.version} module is missing up()`);if(r.VERSION!==void 0&&r.VERSION!==o.version)throw new Error(`MIGRATION_REGISTRY: v${o.version} entry does not match module VERSION (${r.VERSION}). Drift between registry and module \u2014 pick one source of truth.`)}let n=V.length?V[V.length-1].version:0;if(n!==W)throw new Error(`MIGRATION_REGISTRY: highest version v${n} but CURRENT_SCHEMA_VERSION=${W}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let o=0;o<V.length;o++){let r=o+1;if(V[o].version!==r)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${r} at position ${o} but found v${V[o].version}`)}})();function Ut(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
249
249
  [FATAL] DATA_DIR is set to whitespace ("${process.env.DATA_DIR}"). Either unset it or set a real path.
250
250
  `),process.exit(1));try{d.mkdirSync(q,{recursive:!0})}catch(t){t.code!=="EEXIST"&&(console.error(`
251
251
  [FATAL] Cannot create DATA_DIR (${q}): ${t.message}`),console.error(` Fix the path or permissions and try again.
@@ -289,9 +289,9 @@ ${o.join(`
289
289
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`),console.log(" PROFILE VISIT STATS"),console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),console.log(` Unique profiles visited: ${e.totalVisited}`),console.log(` Today: ${e.todayVisited}`),console.log(` Discovered recruiters: ${e.totalRecruiters} (potential visits)`);let t=Te();if(console.log(` Unconnected recruiters: ${t.length}`),ee>0){let n=De(ee);console.log(` Revisit (>${ee}d ago): ${n.length}`)}console.log(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
290
290
  `)}async function Ho(){if(console.log(`
291
291
  LinkedIn Profile Visitor
292
- `),Bo){Vo();return}let e=[],t="";if(ee>0?(e=De(ee),t=`revisit (>${ee} days)`):ve==="people"?(e=Te(),t="unconnected recruiters (people.json)"):ve==="scouted"||ve==="discovered"?(e=_t(),t="discovered recruiters (market-research)"):(e=At({limit:He}),t="smart (new \u2192 active \u2192 unconnected \u2192 revisit)"),console.log(` Source: ${t}`),console.log(` Candidates: ${e.length}`),console.log(` Max visits: ${He}
292
+ `),Bo){Vo();return}let e=[],t="";if(ee>0?(e=De(ee),t=`revisit (>${ee} days)`):ve==="people"?(e=Te(),t="unconnected recruiters (people.json)"):ve==="scouted"||ve==="discovered"?(e=_t(),t="discovered recruiters (market-research)"):(e=Et({limit:He}),t="smart (new \u2192 active \u2192 unconnected \u2192 revisit)"),console.log(` Source: ${t}`),console.log(` Candidates: ${e.length}`),console.log(` Max visits: ${He}
293
293
  `),e.length===0){console.log(` No profiles to visit. Run npm run scout first.
294
- `);return}let n=Ot("visit",ve),{browser:o,page:r}=await ct();try{let s=await Et(r,e,{maxVisits:He,verbose:Uo,skipRecentDays:ee>0?0:7});console.log(`
294
+ `);return}let n=Ot("visit",ve),{browser:o,page:r}=await ct();try{let s=await At(r,e,{maxVisits:He,verbose:Uo,skipRecentDays:ee>0?0:7});console.log(`
295
295
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`),console.log(" Visit Complete"),console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),console.log(` Visited: ${s.visited}`),console.log(` Skipped: ${s.skipped} (visited recently)`),console.log(` Errors: ${s.errors}`),console.log(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
296
296
  `),Oe(n,"success",s)}catch(s){console.error(`
297
297
  Error:`,s.message),Oe(n,"error",null,s.message)}finally{await o.close().catch(()=>{})}}Ho().catch(e=>{console.error("Visit failed:",e),process.exit(1)});
package/dist/warmScan.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import"dotenv/config";import*as j from"fs";import*as a from"path";import{fileURLToPath as L}from"url";import{getPluginConfig as T}from"@ilivemylife/graph-sdk";var k=await T();for(let[e,n]of Object.entries(k))process.env[e]===void 0&&(process.env[e]=n);var z=process.env.LINKEDIN_NAME,p=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),G=Object.freeze(new Set([p.DRAFT,p.CONFIRMED,p.NEEDS_REVIEW])),H=Object.freeze(new Set([p.DRAFT,p.CONFIRMED,p.REJECTED,p.NEEDS_REVIEW]));import"dotenv/config";import*as r from"fs";import*as o from"path";import{fileURLToPath as F}from"url";var R=F(import.meta.url),N=o.dirname(o.dirname(R)),f=process.env.DATA_DIR?o.resolve(process.env.DATA_DIR,"collected-profiles"):o.join(N,"collected-profiles"),ne=o.join(f,"people.json"),te=o.join(f,"jobs.json"),oe=o.join(f,"quota.json"),se=o.join(f,"conversations.json"),re=o.join(f,"sync-state.json"),ie=o.join(f,"application-questions.json"),ce=o.join(f,"profile-history"),A=(e,n)=>{let t=parseInt(e,10);return Number.isInteger(t)&&t>=0?t:n},le=A(process.env.CONNECT_MAX_DAILY,20),ae=A(process.env.CONNECT_MAX_WEEKLY,100);function b(e,n){if(!r.existsSync(e))return null;try{let t=JSON.parse(r.readFileSync(e,"utf-8"));try{r.copyFileSync(e,e+".backup")}catch{}return t}catch(t){console.error(`[!] ${n} is corrupted: ${t.message}`),console.error(` File: ${o.resolve(e)}`);let c=e+".backup";if(r.existsSync(c))try{let s=JSON.parse(r.readFileSync(c,"utf-8"));console.error(` Restored from ${c}`);try{r.copyFileSync(c,e)}catch{}return s}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{r.copyFileSync(e,e+".corrupted")}catch{}return null}}var M=a.dirname(L(import.meta.url)),O=process.env.DATA_DIR?a.resolve(process.env.DATA_DIR,"collected-profiles"):a.join(M,"collected-profiles"),g={};for(let e of process.argv.slice(2)){let n=e.match(/^--([^=]+)(?:=(.*))?$/);n&&(g[n[1]]=n[2]??!0)}var d=g.group||null,C=typeof g["min-seniority"]=="string"?g["min-seniority"]:null,D=!!g["show-all"],u={},h=process.env.DATA_DIR&&a.resolve(process.env.DATA_DIR,"target-companies.json");if(h&&j.existsSync(h))try{u=JSON.parse(j.readFileSync(h,"utf8"))}catch(e){console.error(`Ignoring malformed ${h}: ${e.message}`)}(!u||typeof u!="object"||Array.isArray(u))&&(u={});var E=typeof g.companies=="string"?g.companies.split(","):d?u[d]?.companies:null,y=Array.isArray(E)?E.filter(e=>typeof e=="string"&&e.trim()).map(e=>e.trim()):null;if(!y||!y.length){console.error("Specify which companies to scan:"),console.error(' --companies="Google,Meta,Stripe"');let e=Object.keys(u);d&&!u[d]&&console.error(` (group "${d}" not found)`),e.length?console.error(` --group=<name> your groups: ${e.join(", ")}`):console.error(` or define named groups in ${h||"<DATA_DIR>/target-companies.json"}`),process.exit(1)}var v=b(a.join(O,"people.json"),"people.json")||{},_=b(a.join(O,"conversations.json"),"conversations.json")||{};Object.keys(v).length||(console.error("No contacts found yet \u2014 run `ilml linkedin sync` first to build your network, then warm-scan."),process.exit(1));var w=new Map;for(let[e,n]of Object.entries(_))typeof n.isConnection=="boolean"&&w.set(e,{isConnection:n.isConnection,conv:n});function P(e){if(e.name&&w.has(e.name)){let n=w.get(e.name);return{is:n.isConnection,src:"A.convInbox",conv:n.conv}}return e.connectionDegree==="1st"?{is:!0,src:"B.profileScrape"}:e.connectionDegree==="2nd"||e.connectionDegree==="3rd"?{is:!1,src:"B.profileScrape"}:e.connectionStatus==="connected"?{is:!0,src:"C.botStatus"}:["sent_without_note","sent_with_note","withdrawn","rejected"].includes(e.connectionStatus)?{is:!1,src:"C.botStatus"}:{is:null,src:null}}var I={intern:0,junior:1,mid:2,senior:3,lead:4,staff:4,principal:5,director:5,vp:6};function J(e,n){if(!n)return!0;if(!e)return!1;let t=e.toLowerCase(),c=I[n.toLowerCase()];if(c==null)return!0;for(let[s,i]of Object.entries(I))if(t.includes(s)&&i>=c)return!0;return!1}function q(e,n){return[e.company,e.title,n?.personContext?.title,n?.personContext?.company].filter(Boolean).join(" | ").toLowerCase()}function B(e){let n=e.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${n}\\b`,"i")}var U=y.map(e=>({name:e,re:B(e)})),S={};for(let e of y)S[e]={connected:[],notConnected:[],unknown:[]};for(let[e,n]of Object.entries(v)){let t=n.name?_[n.name]:null,c=q(n,t);if(c)for(let{name:s,re:i}of U){if(!i.test(c))continue;if(!J(n.title||t?.personContext?.title,C))break;let l=P(n),m={name:n.name||e.slice(-40),title:n.title||t?.personContext?.title||"",company:n.company,degree:n.connectionDegree,via:l.src,convStatus:l.conv?.conversationStatus,lastMsg:l.conv?.lastMessageTime,lastBy:l.conv?.lastMessageBy,category:l.conv?.category||n.category};l.is===!0?S[s].connected.push(m):l.is===!1?S[s].notConnected.push(m):S[s].unknown.push(m);break}}var V=C?` (min-seniority=${C})`:"",W=d?`group "${d}"`:`${y.length} target companies`;console.log(`
2
- \u2550\u2550 Warm-scan: ${W}${V} \u2550\u2550
3
- `);var Q=Object.entries(S).sort((e,n)=>n[1].connected.length-e[1].connected.length),$=0;for(let[e,n]of Q){let t=n.connected.length+n.notConnected.length+n.unknown.length;$+=n.connected.length;let c=`${e.padEnd(18)} connected: ${n.connected.length}, other: ${n.notConnected.length+n.unknown.length}, total seen: ${t}`;if(console.log(c),n.connected.length>0)for(let i of n.connected){let l=(i.title||"").slice(0,55),m=i.convStatus?` [${i.convStatus}]`:"",x=i.lastMsg?` (${i.lastMsg.slice(0,10)})`:"";console.log(` \xB7 ${i.name.padEnd(28)} ${l}${m}${x}`)}let s=[...n.notConnected,...n.unknown];if(s.length>0&&(D||s.length<=5)){console.log(" seen but not 1st-degree:");for(let i of s.slice(0,D?s.length:5))console.log(` ${i.name.padEnd(28)} ${(i.title||"").slice(0,55)}`)}else s.length>5&&console.log(` seen but not 1st-degree: ${s.length} \u2014 add --show-all or --companies="${e}" to expand`);console.log("")}console.log(`Total 1st-degree at target companies: ${$}`);
1
+ import"dotenv/config";import*as j from"fs";import*as a from"path";import{fileURLToPath as L}from"url";import{getPluginConfig as T}from"@ilivemylife/graph-sdk";var k=await T();for(let[e,n]of Object.entries(k))process.env[e]===void 0&&(process.env[e]=n);var z=process.env.LINKEDIN_NAME,p=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),G=Object.freeze(new Set([p.DRAFT,p.CONFIRMED,p.NEEDS_REVIEW])),H=Object.freeze(new Set([p.DRAFT,p.CONFIRMED,p.REJECTED,p.NEEDS_REVIEW]));import"dotenv/config";import*as r from"fs";import*as o from"path";import{fileURLToPath as F}from"url";var R=F(import.meta.url),N=o.dirname(o.dirname(R)),f=process.env.DATA_DIR?o.resolve(process.env.DATA_DIR,"collected-profiles"):o.join(N,"collected-profiles"),ne=o.join(f,"people.json"),te=o.join(f,"jobs.json"),oe=o.join(f,"quota.json"),se=o.join(f,"conversations.json"),re=o.join(f,"sync-state.json"),ie=o.join(f,"application-questions.json"),ce=o.join(f,"profile-history"),A=(e,n)=>{let t=parseInt(e,10);return Number.isInteger(t)&&t>=0?t:n},le=A(process.env.CONNECT_MAX_DAILY,20),ae=A(process.env.CONNECT_MAX_WEEKLY,100);function b(e,n){if(!r.existsSync(e))return null;try{let t=JSON.parse(r.readFileSync(e,"utf-8"));try{r.copyFileSync(e,e+".backup")}catch{}return t}catch(t){console.error(`[!] ${n} is corrupted: ${t.message}`),console.error(` File: ${o.resolve(e)}`);let c=e+".backup";if(r.existsSync(c))try{let s=JSON.parse(r.readFileSync(c,"utf-8"));console.error(` Restored from ${c}`);try{r.copyFileSync(c,e)}catch{}return s}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{r.copyFileSync(e,e+".corrupted")}catch{}return null}}var M=a.dirname(L(import.meta.url)),O=process.env.DATA_DIR?a.resolve(process.env.DATA_DIR,"collected-profiles"):a.join(M,"collected-profiles"),g={};for(let e of process.argv.slice(2)){let n=e.match(/^--([^=]+)(?:=(.*))?$/);n&&(g[n[1]]=n[2]??!0)}var d=g.group||null,C=typeof g["min-seniority"]=="string"?g["min-seniority"]:null,D=!!g["show-all"],u={},h=process.env.DATA_DIR&&a.resolve(process.env.DATA_DIR,"target-companies.json");if(h&&j.existsSync(h))try{u=JSON.parse(j.readFileSync(h,"utf8"))}catch(e){console.error(`Ignoring malformed ${h}: ${e.message}`)}(!u||typeof u!="object"||Array.isArray(u))&&(u={});var I=typeof g.companies=="string"?g.companies.split(","):d?u[d]?.companies:null,y=Array.isArray(I)?I.filter(e=>typeof e=="string"&&e.trim()).map(e=>e.trim()):null;if(!y||!y.length){console.error("Specify which companies to scan:"),console.error(' --companies="Google,Meta,Stripe"');let e=Object.keys(u);d&&!u[d]&&console.error(` (group "${d}" not found)`),e.length?console.error(` --group=<name> your groups: ${e.join(", ")}`):console.error(` or define named groups in ${h||"<DATA_DIR>/target-companies.json"}`),process.exit(1)}var v=b(a.join(O,"people.json"),"people.json")||{},_=b(a.join(O,"conversations.json"),"conversations.json")||{};Object.keys(v).length||(console.error("No contacts found yet \u2014 run `ilml linkedin sync` first to build your network, then warm-scan."),process.exit(1));var w=new Map;for(let[e,n]of Object.entries(_))typeof n.isConnection=="boolean"&&w.set(e,{isConnection:n.isConnection,conv:n});function P(e){if(e.name&&w.has(e.name)){let n=w.get(e.name);return{is:n.isConnection,src:"A.convInbox",conv:n.conv}}return e.connectionDegree==="1st"?{is:!0,src:"B.profileScrape"}:e.connectionDegree==="2nd"||e.connectionDegree==="3rd"?{is:!1,src:"B.profileScrape"}:e.connectionStatus==="connected"?{is:!0,src:"C.botStatus"}:["sent_without_note","sent_with_note","withdrawn","rejected"].includes(e.connectionStatus)?{is:!1,src:"C.botStatus"}:{is:null,src:null}}var E={intern:0,junior:1,mid:2,senior:3,lead:4,staff:4,principal:5,director:5,vp:6};function J(e,n){if(!n)return!0;if(!e)return!1;let t=e.toLowerCase(),c=E[n.toLowerCase()];if(c==null)return!0;for(let[s,i]of Object.entries(E))if(t.includes(s)&&i>=c)return!0;return!1}function q(e,n){return[e.company,e.title,n?.personContext?.title,n?.personContext?.company].filter(Boolean).join(" | ").toLowerCase()}function B(e){let n=e.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${n}\\b`,"i")}var V=y.map(e=>({name:e,re:B(e)})),S={};for(let e of y)S[e]={connected:[],notConnected:[],unknown:[]};for(let[e,n]of Object.entries(v)){let t=n.name?_[n.name]:null,c=q(n,t);if(c)for(let{name:s,re:i}of V){if(!i.test(c))continue;if(!J(n.title||t?.personContext?.title,C))break;let l=P(n),m={name:n.name||e.slice(-40),title:n.title||t?.personContext?.title||"",company:n.company,degree:n.connectionDegree,via:l.src,convStatus:l.conv?.conversationStatus,lastMsg:l.conv?.lastMessageTime,lastBy:l.conv?.lastMessageBy,category:l.conv?.category||n.category};l.is===!0?S[s].connected.push(m):l.is===!1?S[s].notConnected.push(m):S[s].unknown.push(m);break}}var U=C?` (min-seniority=${C})`:"",W=d?`group "${d}"`:`${y.length} target companies`;console.log(`
2
+ \u2550\u2550 Warm-scan: ${W}${U} \u2550\u2550
3
+ `);var Q=Object.entries(S).sort((e,n)=>n[1].connected.length-e[1].connected.length),x=0;for(let[e,n]of Q){let t=n.connected.length+n.notConnected.length+n.unknown.length;x+=n.connected.length;let c=`${e.padEnd(18)} connected: ${n.connected.length}, other: ${n.notConnected.length+n.unknown.length}, total seen: ${t}`;if(console.log(c),n.connected.length>0)for(let i of n.connected){let l=(i.title||"").slice(0,55),m=i.convStatus?` [${i.convStatus}]`:"",$=i.lastMsg?` (${i.lastMsg.slice(0,10)})`:"";console.log(` \xB7 ${i.name.padEnd(28)} ${l}${m}${$}`)}let s=[...n.notConnected,...n.unknown];if(s.length>0&&(D||s.length<=5)){console.log(" seen but not 1st-degree:");for(let i of s.slice(0,D?s.length:5))console.log(` ${i.name.padEnd(28)} ${(i.title||"").slice(0,55)}`)}else s.length>5&&console.log(` seen but not 1st-degree: ${s.length} \u2014 add --show-all or --companies="${e}" to expand`);console.log("")}console.log(`Total 1st-degree at target companies: ${x}`);
package/ilml-plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data with AI-assisted workflows",
5
5
  "commands": {
6
6
  "help": { "run": "node dist/help.mjs", "description": "List all commands with descriptions; or `help <cmd>` for detailed info on one command (usage, flags, ban-risk, when-to-use). Reads ilml-plugin.json directly.", "usage": "ilml linkedin help [<cmd>]", "useWhen": "You forgot what a command does, or you're an AI agent picking the right tool." },
@@ -15,6 +15,20 @@
15
15
  "useWhen": "You have an active job search and want the plugin to drain your daily Easy-Apply queue."
16
16
  },
17
17
  "sync": { "run": "node dist/syncAll.mjs", "description": "Alias for sync-all.", "usage": "ilml linkedin sync [flags…]" },
18
+ "sync-connections": {
19
+ "run": "node dist/syncConnections.mjs",
20
+ "description": "Scan your LinkedIn 1st-degree connections list and update the local contacts DB. This is the ONLY command that bulk-detects who ACCEPTED your connection requests — it flips them to 'connected', records the accept date, and imports new connections. Run it after sending requests so acceptance analytics (quota) and dedup stay accurate. Distinct from sync/sync-all, which handle your inbox/messages — not the connections list.",
21
+ "usage": "ilml linkedin sync-connections [--full] [--max=N] [--list] [--dry-run] [--verbose]",
22
+ "flags": {
23
+ "--full": "Scan the ENTIRE connections list, not just recently-added. Slower; run periodically to catch drift.",
24
+ "--max=N": "Stop after extracting N connections.",
25
+ "--list": "Show DB connection stats only. No browser.",
26
+ "--dry-run": "Scrape but don't write to the DB.",
27
+ "--verbose": "Extra logging."
28
+ },
29
+ "banRisk": "Low. Viewing your own connections list is normal LinkedIn activity. The FIRST run scans your whole connections list (nothing is synced yet, so it can't stop early); subsequent runs are incremental — they scroll only until they reach already-synced connections. Use --max to cap the first run if you want.",
30
+ "useWhen": "Daily, or right after a `connect run` batch — to learn who accepted your invites and keep acceptance/dedup accurate."
31
+ },
18
32
  "sync-all": {
19
33
  "run": "node dist/syncAll.mjs",
20
34
  "description": "Pull your LinkedIn inbox into the local DB. Phase 1 scans the sidebar for new/changed threads, Phase 2 opens each changed thread and saves its messages, Phase 3 enriches (categorise, summarise, suggest action), Phase 4 prints a report. Preserves LinkedIn's bold 'unread' badges by default. Saves to people.json + conversations.json. Atomic writes throughout — safe to Ctrl-C.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilml-plugin-linkedin",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data and run AI-assisted workflows under your direction.",
5
5
  "type": "module",
6
6
  "files": [