ilml-plugin-linkedin 1.20.11 → 1.20.13
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 +16 -0
- package/dist/applyQueue.mjs +26 -26
- package/dist/connect.mjs +52 -52
- package/dist/daily.mjs +33 -33
- package/dist/enrichProfile.mjs +16 -16
- package/dist/enrichProfiles.mjs +51 -51
- package/dist/funnelRunner.mjs +44 -44
- package/dist/messageBot.mjs +40 -40
- package/dist/migrationCli.mjs +43 -43
- package/dist/profileHistory.mjs +8 -8
- package/dist/quota.mjs +32 -32
- package/dist/readPost.mjs +9 -9
- package/dist/run.mjs +69 -66
- package/dist/scout.mjs +40 -39
- package/dist/src/saveLoginState.mjs +4 -4
- package/dist/syncAll.mjs +87 -87
- package/dist/syncConnections.mjs +30 -30
- package/dist/verifyAbout.mjs +19 -19
- package/dist/version.mjs +1 -1
- package/dist/viewers.mjs +43 -43
- package/dist/visit.mjs +50 -50
- package/dist/warmScan.mjs +3 -3
- package/ilml-plugin.json +11 -2
- package/package.json +2 -2
package/dist/visit.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
1
|
+
var qn=Object.defineProperty;var Y=(e,t)=>()=>(e&&(t=e(e=0)),t);var pe=(e,t)=>{for(var n in t)qn(e,n,{get:t[n],enumerable:!0})};import D from"fs";import V from"path";function Le(){let e=process.env.ILML_SCOPE_DIR;return e?V.join(e,"plugins-state","linkedin","cookies.json"):Un}function Bn(){let e=Le(),t=V.dirname(e);return t&&t!=="."&&!D.existsSync(t)&&D.mkdirSync(t,{recursive:!0}),e}function bt(e){let t=Bn();return D.writeFileSync(t,JSON.stringify(e)),t}function vt(){let e=Le();if(!D.existsSync(e))throw new Error(`Cookies not found at ${e}. Run: ilml linkedin login`);let t;try{t=D.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}function kt(){return D.existsSync(Le())}function Vn(){let e=process.env.ILML_SCOPE_DIR;return e?V.join(e,"plugins-state","linkedin"):"."}function Ct(e){let t=String(e||"").replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,80);return/[a-zA-Z0-9]/.test(t)?t:"default"}function Hn(){let e=process.env.MY_LINKEDIN_URL,t=e&&e.match(/\/in\/([^/?#]+)/);return t&&t[1]?Ct(t[1]):"default"}function xt(e){let t=process.env.LINKEDIN_CHROME_PROFILE_DIR;if(t)return D.existsSync(t)||D.mkdirSync(t,{recursive:!0}),V.resolve(t);let n=Ct(e||Hn()),o=V.join(Vn(),"chrome-profile",n);return D.existsSync(o)||D.mkdirSync(o,{recursive:!0}),V.resolve(o)}function Et(e){return!D.existsSync(V.join(e,At))}function _t(e){try{D.writeFileSync(V.join(e,At),new Date().toISOString())}catch{}}function $t(e){let t=0;for(let n of["SingletonLock","SingletonCookie","SingletonSocket"])try{D.rmSync(V.join(e,n),{force:!0}),t++}catch{}return t}var Un,At,Ee=Y(()=>{Un="cookies.json";At=".ilml-seeded"});async function x(e){let t=Math.floor(Math.random()*2001),n=e+t;return new Promise(o=>setTimeout(o,n))}async function Jn(e,t,{timeout:n=45e3,poll:o=1e3,label:r="page"}={}){let s=Date.now();for(;Date.now()-s<n;){try{if(await e.evaluate(a=>!!document.querySelector(a),t)){let a=Date.now()-s;return a>6e3&&console.log(` (waited ${Math.round(a/1e3)}s for LinkedIn to finish loading ${r})`),!0}}catch{}await new Promise(i=>setTimeout(i,o))}return!1}function Wn(e){return Rt.test(e||"")}async function Gn(e,{credentialWaitMs:t=36e4,label:n="page"}={}){console.log(` \u26A0 Auth wall on ${n} \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 o=!!(process.stdin&&process.stdin.isTTY),r=o?t:Math.min(t,45e3),s=Date.now(),i=!1;for(;Date.now()-s<r;){let l="wait";try{l=await e.evaluate(d=>!new RegExp(d).test(location.href)&&document.querySelector("#global-nav, nav.global-nav")?"in":document.querySelector('input#username, input[name="session_key"], input#password')?"creds":"wait",Rt.source)}catch{}if(l==="in"){try{let d=await e.cookies();if(Array.isArray(d)&&d.some(y=>y.name==="li_at")){let y=bt(d);console.log(` \u2713 Session recovered \u2014 re-saved ${d.length} cookies (${y}).`)}else console.log(" \u2713 Session recovered (kept existing cookie snapshot \u2014 refreshed set had no li_at).")}catch(d){console.log(` \u2713 Session recovered (cookie re-save skipped: ${d.message}).`)}return!0}if(l==="creds"){if(!o){let d=new Error(`Session expired (${n}) \u2014 credentials required and no interactive terminal. Run: ilml linkedin login`);throw d.code="SESSION_EXPIRED",d}i||(i=!0,console.log(`
|
|
2
2
|
\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.
|
|
3
|
-
`))}await new Promise(a=>setTimeout(a,2500))}let c=new Error(`Session expired (${n}) \u2014 not signed in within the wait window. Run: ilml linkedin login`);throw c.code="SESSION_EXPIRED",c}async function Ee(e,{readySelector:t,label:n="page",timeout:o=45e3}={}){let r=await Un(e,t,{timeout:o,label:n});if(!r&&Bn(e.url())){let s=new Error(`Session expired (${n}) \u2014 redirected to login. Run: ilml linkedin login`);throw s.code="SESSION_EXPIRED",s}return r}async function Fe(e,t,{readySelector:n,label:o="page",timeout:r=45e3,gotoTimeout:s=6e4,autoRecover:i=!0}={}){await e.goto(t,{waitUntil:"domcontentloaded",timeout:s});try{return await Ee(e,{readySelector:n,label:o,timeout:r})}catch(c){if(i&&c.code==="SESSION_EXPIRED"){await Vn(e,{label:o});try{await e.goto(t,{waitUntil:"domcontentloaded",timeout:s})}catch{}return await Ee(e,{readySelector:n,label:o,timeout:r})}throw c}}async function Dt(e){return await e.evaluate(()=>{let t=document.querySelectorAll("div.artdeco-modal--is-open");for(let o of t){let r=o.innerText||"",s=r.includes("Premium")||r.includes("premium")||r.includes("Upgrade")||r.includes("upgrade"),i=r.includes("Try for")||r.includes("free trial")||r.includes("/month")||r.includes("per month")||r.includes("Start my")||r.includes("Subscribe"),c=r.includes("Get hired faster")||r.includes("Get hired 2x");if(s||i||c){let l=o.querySelector('button[aria-label="Dismiss"]')||o.querySelector("button.artdeco-modal__dismiss")||o.querySelector("button[data-test-modal-close-btn]");if(l)return l.click(),{dismissed:!0,matched:r.substring(0,80)};let a=o.querySelectorAll("button");for(let d of a){let p=d.innerText?.trim().toLowerCase()||"",u=d.getAttribute("aria-label")?.toLowerCase()||"";if(p==="not now"||p==="no thanks"||p==="dismiss"||u.includes("dismiss"))return d.click(),{dismissed:!0,matched:r.substring(0,80)}}}}let n=document.querySelector(".premium-upsell-link")||document.querySelector("[data-test-premium-upsell]")||document.querySelector(".premium-hub-upsell");if(n){let o=n.closest('[class*="modal"]')?.querySelector('button[aria-label="Dismiss"]')||n.closest('[class*="banner"]')?.querySelector('button[aria-label="Close"]');if(o)return o.click(),{dismissed:!0,matched:"premium-banner"}}return!1})}var Rt,pe=z(()=>{Ae();Rt=/\/authwall|\/login|\/checkpoint|\/uas\/login/});import{getPluginConfig as Hn}from"@ilivemylife/graph-sdk";var Jn,Wn,Q,Vr,Gn,Pe=z(async()=>{Jn=await Hn();for(let[e,t]of Object.entries(Jn))process.env[e]===void 0&&(process.env[e]=t);Wn=process.env.LINKEDIN_NAME,Q=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Vr=Object.freeze(new Set([Q.DRAFT,Q.CONFIRMED,Q.NEEDS_REVIEW])),Gn=Object.freeze(new Set([Q.DRAFT,Q.CONFIRMED,Q.REJECTED,Q.NEEDS_REVIEW]))});import"dotenv/config";import*as A from"fs";import*as $ from"path";import{fileURLToPath as oo}from"url";function Wt(){le=null,io=null,lo=null,ao=null,co=null,uo=null}function Gt(){A.existsSync(V)||A.mkdirSync(V,{recursive:!0})}function Yt(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 Kt(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: ${$.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 fo(){return new Date().toISOString().split("T")[0]}function X(e){if(!e)return null;let t=e.match(/\/in\/([^/?#]+)/);return t?t[1].toLowerCase():null}function Bt(e){return typeof e=="string"?e.replace(/\s+/g," ").trim().toLowerCase():""}function zt(e,t,n=null){if(!t||e[t])return t;let o=X(t);if(!o)return t;let r=null;for(let[s,i]of Object.entries(e)){if(X(i.profileUrl||s)===o)return s;!r&&Array.isArray(i.aliases)&&i.aliases.includes(o)&&(r=s)}if(r){let s=Bt(n),i=Bt(e[r].name);if(s&&i&&s===i)return r}return t}function ho(){A.existsSync(Be)||A.mkdirSync(Be,{recursive:!0})}function Qt(e){let t=X(e);return t?$.join(Be,`${t}.json`):null}function go(e){let t=Qt(e);return t?Kt(t,`profile-history/${X(e)}.json`)||[]:[]}function yo(e,t){let n=Qt(e);if(!n)return;ho();let o=go(e);o.unshift(t);try{Yt(n,o)}catch(r){console.error(`[!] Failed to append profile-history for ${X(e)}: ${r.message}`)}}function Vt(e){return!!(e==null||Array.isArray(e)&&e.length===0||typeof e=="string"&&!e.trim())}function Ht(e){return Array.isArray(e)?[...e].map(t=>String(t).trim()).sort().join("||"):typeof e=="string"?e.replace(/\s+/g," ").trim():e}function wo(e,t){if(!e)return[];let n=[];for(let o of po){let r=e[o],s=t[o];Vt(r)||Vt(s)||Ht(r)!==Ht(s)&&n.push(o)}return n}function bo(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 le||(Gt(),le=Kt(Ue,"people.json")||{},le)}function Xt(e){Gt(),le=e;try{Yt(Ue,e)}catch(t){console.error(`[!] Failed to save people.json: ${t.message}`),console.error(` Path: ${$.resolve(Ue)}`)}}function Ve(e,t={}){if(!e)return null;let n=ae(),o=fo(),r=new Date().toISOString(),s=zt(n,e,t.name),i=n[s];if(i){let c=wo(i,t);c.length>0&&(yo(s,{capturedAt:r,previousLastUpdated:i.lastUpdated||null,changedFields:c,snapshot:bo(i)}),i.lastSnapshotAt=r),i.seenCount=(i.seenCount||1)+1,i.lastSeen=o,i.lastUpdated=r,t.source&&!i.sources?.includes(t.source)&&(i.sources=i.sources||[],i.sources.push(t.source));for(let l of mo)if(t[l]!=null){if(Array.isArray(t[l])&&t[l].length===0&&Array.isArray(i[l])&&i[l].length>0)continue;i[l]=t[l]}Array.isArray(t.availableSections)&&(i.lastFullVisitAt=r)}else n[s]={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 Xt(n),n[s]}function He(e,t,n={}){if(n.dryRun||!e||!t)return;let o=X(t);if(!o)return;let r=ae(),s=zt(r,e),i=r[s];if(!i)return;let c=X(i.profileUrl||s);o!==c&&(Array.isArray(i.aliases)||(i.aliases=[]),i.aliases.includes(o)||(i.aliases.push(o),i.lastUpdated=new Date().toISOString(),Xt(r)))}var ro,so,V,Ue,us,ds,fs,ms,ps,Be,Jt,hs,gs,le,io,lo,ao,co,uo,mo,po,_e=z(async()=>{await Pe();ro=oo(import.meta.url),so=$.dirname($.dirname(ro)),V=process.env.DATA_DIR?$.resolve(process.env.DATA_DIR,"collected-profiles"):$.join(so,"collected-profiles"),Ue=$.join(V,"people.json"),us=$.join(V,"jobs.json"),ds=$.join(V,"quota.json"),fs=$.join(V,"conversations.json"),ms=$.join(V,"sync-state.json"),ps=$.join(V,"application-questions.json"),Be=$.join(V,"profile-history"),Jt=(e,t)=>{let n=parseInt(e,10);return Number.isInteger(n)&&n>=0?n:t},hs=Jt(process.env.CONNECT_MAX_DAILY,20),gs=Jt(process.env.CONNECT_MAX_WEEKLY,100),le=null,io=null,lo=null,ao=null,co=null,uo=null;mo=["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"],po=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"]});var Zt={};me(Zt,{extractCertificationsFromDetails:()=>xo,extractEducationFromDetails:()=>ko,extractExperienceFromDetails:()=>So,extractFeaturedFromDetails:()=>vo,extractRecommendationsFromDetails:()=>Ao,extractSkillsFromDetails:()=>Co});async function So(e,t){let n=t.replace(/\/+$/,"")+"/details/experience/";try{await e.goto(n,{waitUntil:"domcontentloaded",timeout:6e4}),await Ee(e,{readySelector:"main .pvs-list, .scaffold-layout__main, main.scaffold-layout__main, section.artdeco-card",label:"experience details"}),await C(1e3);for(let r=0;r<5;r++)await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await C(900);await C(1e3);let o=0;for(;o<5&&await e.evaluate(()=>{for(let s of document.querySelectorAll("button"))if(/^Load more$/i.test((s.textContent||"").trim()))return s.click(),!0;return!1});)o++,await C(1500),await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await C(700);return await e.evaluate(()=>{let r=document.querySelector("main");if(!r)return[];function s(a){let d=[];for(let p of a.querySelectorAll("div")){let u=Array.from(p.children).filter(E=>E.tagName==="DIV");if(u.length<3)continue;let m=u.filter(E=>(E.textContent||"").trim().length>50);m.length<3||(p.textContent||"").length<500||d.push({el:p,count:m.length,totalText:(p.textContent||"").length})}return d.sort((p,u)=>u.count-p.count||p.totalText-u.totalText),d[0]?.el}let i=s(r);if(!i)return[];let c=Array.from(i.children).filter(a=>a.tagName==="DIV").filter(a=>(a.textContent||"").trim().length>50);function l(a){let d=a[0]||null,p=null,u=null;if(a[1]){let w=a[1].split("\xB7").map(T=>T.trim());p=w[0]||null,w[1]&&(u=w[1])}let m=a.findIndex(w=>/(\d{4}|present)/i.test(w)&&/(-|–|—)/i.test(w)&&w.length<80),E=null,k=null;if(m>=0){let w=a[m].split("\xB7").map(T=>T.trim());E=w[0]||null,w[1]&&(k=w[1])}let _=null,y=-1;if(m>=0&&a[m+1]){let w=a[m+1],T=/^[>*\-•]|^>/.test(w),de=/https?:\/\//.test(w),se=/\d{4}/.test(w),ie=/,/.test(w)||/\bArea\b|\bRegion\b|\bProvince\b|\bCountry\b|, [A-Z]{2}\b/.test(w);!T&&!de&&!se&&w.length<100&&ie&&(_=w,y=m+1)}let g=null,U=-1,x=a[a.length-1];if(x&&/and\s+\+\d+\s+skills?$/i.test(x)){U=a.length-1;let w=x.match(/^(.+?)\s+and\s+\+(\d+)\s+skills?$/i);w&&(g={listed:w[1].split(",").map(T=>T.trim()),additional:parseInt(w[2],10)})}let ue=new Set([0,1,m,y,U].filter(w=>w>=0)),J=a.filter((w,T)=>!ue.has(T)),K=J.length>0?J.join(`
|
|
3
|
+
`))}await new Promise(d=>setTimeout(d,2500))}let a=new Error(`Session expired (${n}) \u2014 not signed in within the wait window. Run: ilml linkedin login`);throw a.code="SESSION_EXPIRED",a}async function It(e,{readySelector:t,label:n="page",timeout:o=45e3}={}){let r=await Jn(e,t,{timeout:o,label:n});if(!r&&Wn(e.url())){let s=new Error(`Session expired (${n}) \u2014 redirected to login. Run: ilml linkedin login`);throw s.code="SESSION_EXPIRED",s}return r}async function ce(e,t,{readySelector:n,label:o="page",timeout:r=45e3,gotoTimeout:s=6e4,autoRecover:i=!0}={}){await e.goto(t,{waitUntil:"domcontentloaded",timeout:s});try{return await It(e,{readySelector:n,label:o,timeout:r})}catch(a){if(i&&a.code==="SESSION_EXPIRED"){await Gn(e,{label:o});try{await e.goto(t,{waitUntil:"domcontentloaded",timeout:s})}catch{}return await It(e,{readySelector:n,label:o,timeout:r})}throw a}}async function Dt(e){return await e.evaluate(()=>{let t=document.querySelectorAll("div.artdeco-modal--is-open");for(let o of t){let r=o.innerText||"",s=r.includes("Premium")||r.includes("premium")||r.includes("Upgrade")||r.includes("upgrade"),i=r.includes("Try for")||r.includes("free trial")||r.includes("/month")||r.includes("per month")||r.includes("Start my")||r.includes("Subscribe"),a=r.includes("Get hired faster")||r.includes("Get hired 2x");if(s||i||a){let l=o.querySelector('button[aria-label="Dismiss"]')||o.querySelector("button.artdeco-modal__dismiss")||o.querySelector("button[data-test-modal-close-btn]");if(l)return l.click(),{dismissed:!0,matched:r.substring(0,80)};let d=o.querySelectorAll("button");for(let c of d){let y=c.innerText?.trim().toLowerCase()||"",u=c.getAttribute("aria-label")?.toLowerCase()||"";if(y==="not now"||y==="no thanks"||y==="dismiss"||u.includes("dismiss"))return c.click(),{dismissed:!0,matched:r.substring(0,80)}}}}let n=document.querySelector(".premium-upsell-link")||document.querySelector("[data-test-premium-upsell]")||document.querySelector(".premium-hub-upsell");if(n){let o=n.closest('[class*="modal"]')?.querySelector('button[aria-label="Dismiss"]')||n.closest('[class*="banner"]')?.querySelector('button[aria-label="Close"]');if(o)return o.click(),{dismissed:!0,matched:"premium-banner"}}return!1})}var Rt,he=Y(()=>{Ee();Rt=/\/authwall|\/login|\/checkpoint|\/uas\/login/});function Kn(e){return String(e??"").split(/[,\s|]+/).map(t=>t.trim()).filter(Boolean)}function Ot(e=process.env){let t=(e.JOB_SEARCH_KEYWORDS||"").trim();if(!t)return null;let n=new URLSearchParams;n.set("keywords",t);let o=(e.JOB_SEARCH_LOCATION||"").trim();o&&n.set("location",o);let r=(e.JOB_SEARCH_GEO_ID||"").trim();r&&n.set("geoId",r);let s=parseInt(e.JOB_SEARCH_RADIUS_MILES,10);Number.isFinite(s)&&s>0&&n.set("distance",String(s)),Tt(e.JOB_SEARCH_EASY_APPLY)&&n.set("f_AL","true"),Tt(e.JOB_SEARCH_IN_NETWORK)&&n.set("f_JIYN","true");let i=Kn(e.JOB_SEARCH_COMPANY_IDS);i.length&&n.set("f_C",i.join(","));let a=(e.JOB_SEARCH_DATE_POSTED||"").trim().toLowerCase();a&&n.set("f_TPR",Yn[a]||a),n.set("origin","JOB_SEARCH_PAGE_JOB_FILTER");let l="https://www.linkedin.com/jobs/search/?"+n.toString(),d=(e.JOB_SEARCH_EXTRA_PARAMS||"").trim().replace(/^[?&]+/,"");return d&&(l+="&"+d),l}var Yn,Tt,jt=Y(()=>{Yn={day:"r86400","24h":"r86400",today:"r86400",week:"r604800","7d":"r604800",month:"r2592000","30d":"r2592000"},Tt=e=>/^(1|true|yes|on)$/i.test(String(e??"").trim())});import{getPluginConfig as zn}from"@ilivemylife/graph-sdk";var Qn,Nt,Xn,Z,Qr,Zn,Pe=Y(async()=>{jt();Qn=await zn();for(let[e,t]of Object.entries(Qn))process.env[e]===void 0&&(process.env[e]=t);Nt=Ot(process.env);Nt&&(process.env.LINKEDIN_SEARCH_URL=Nt);Xn=process.env.LINKEDIN_NAME,Z=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Qr=Object.freeze(new Set([Z.DRAFT,Z.CONFIRMED,Z.NEEDS_REVIEW])),Zn=Object.freeze(new Set([Z.DRAFT,Z.CONFIRMED,Z.REJECTED,Z.NEEDS_REVIEW]))});import"dotenv/config";import*as A from"fs";import*as _ from"path";import{fileURLToPath as co}from"url";function zt(){ue=null,mo=null,po=null,ho=null,go=null,yo=null}function Qt(){A.existsSync(H)||A.mkdirSync(H,{recursive:!0})}function Xt(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 Zt(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: ${_.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 wo(){return new Date().toISOString().split("T")[0]}function ee(e){if(!e)return null;let t=e.match(/\/in\/([^/?#]+)/);return t?t[1].toLowerCase():null}function Wt(e){return typeof e=="string"?e.replace(/\s+/g," ").trim().toLowerCase():""}function en(e,t,n=null){if(!t||e[t])return t;let o=ee(t);if(!o)return t;let r=null;for(let[s,i]of Object.entries(e)){if(ee(i.profileUrl||s)===o)return s;!r&&Array.isArray(i.aliases)&&i.aliases.includes(o)&&(r=s)}if(r){let s=Wt(n),i=Wt(e[r].name);if(s&&i&&s===i)return r}return t}function vo(){A.existsSync(Ue)||A.mkdirSync(Ue,{recursive:!0})}function tn(e){let t=ee(e);return t?_.join(Ue,`${t}.json`):null}function ko(e){let t=tn(e);return t?Zt(t,`profile-history/${ee(e)}.json`)||[]:[]}function Co(e,t){let n=tn(e);if(!n)return;vo();let o=ko(e);o.unshift(t);try{Xt(n,o)}catch(r){console.error(`[!] Failed to append profile-history for ${ee(e)}: ${r.message}`)}}function Gt(e){return!!(e==null||Array.isArray(e)&&e.length===0||typeof e=="string"&&!e.trim())}function Yt(e){return Array.isArray(e)?[...e].map(t=>String(t).trim()).sort().join("||"):typeof e=="string"?e.replace(/\s+/g," ").trim():e}function xo(e,t){if(!e)return[];let n=[];for(let o of bo){let r=e[o],s=t[o];Gt(r)||Gt(s)||Yt(r)!==Yt(s)&&n.push(o)}return n}function Ao(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 de(){return ue||(Qt(),ue=Zt(qe,"people.json")||{},ue)}function nn(e){Qt(),ue=e;try{Xt(qe,e)}catch(t){console.error(`[!] Failed to save people.json: ${t.message}`),console.error(` Path: ${_.resolve(qe)}`)}}function Be(e,t={}){if(!e)return null;let n=de(),o=wo(),r=new Date().toISOString(),s=en(n,e,t.name),i=n[s];if(i){let a=xo(i,t);a.length>0&&(Co(s,{capturedAt:r,previousLastUpdated:i.lastUpdated||null,changedFields:a,snapshot:Ao(i)}),i.lastSnapshotAt=r),i.seenCount=(i.seenCount||1)+1,i.lastSeen=o,i.lastUpdated=r,t.source&&!i.sources?.includes(t.source)&&(i.sources=i.sources||[],i.sources.push(t.source));for(let l of So)if(t[l]!=null){if(Array.isArray(t[l])&&t[l].length===0&&Array.isArray(i[l])&&i[l].length>0)continue;i[l]=t[l]}Array.isArray(t.availableSections)&&(i.lastFullVisitAt=r)}else n[s]={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 nn(n),n[s]}function Ve(e,t,n={}){if(n.dryRun||!e||!t)return;let o=ee(t);if(!o)return;let r=de(),s=en(r,e),i=r[s];if(!i)return;let a=ee(i.profileUrl||s);o!==a&&(Array.isArray(i.aliases)||(i.aliases=[]),i.aliases.includes(o)||(i.aliases.push(o),i.lastUpdated=new Date().toISOString(),nn(r)))}var uo,fo,H,qe,ws,Ss,bs,vs,ks,Ue,Kt,Cs,xs,ue,mo,po,ho,go,yo,So,bo,$e=Y(async()=>{await Pe();uo=co(import.meta.url),fo=_.dirname(_.dirname(uo)),H=process.env.DATA_DIR?_.resolve(process.env.DATA_DIR,"collected-profiles"):_.join(fo,"collected-profiles"),qe=_.join(H,"people.json"),ws=_.join(H,"jobs.json"),Ss=_.join(H,"quota.json"),bs=_.join(H,"conversations.json"),vs=_.join(H,"sync-state.json"),ks=_.join(H,"application-questions.json"),Ue=_.join(H,"profile-history"),Kt=(e,t)=>{let n=parseInt(e,10);return Number.isInteger(n)&&n>=0?n:t},Cs=Kt(process.env.CONNECT_MAX_DAILY,20),xs=Kt(process.env.CONNECT_MAX_WEEKLY,100),ue=null,mo=null,po=null,ho=null,go=null,yo=null;So=["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"],bo=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"]});var on={};pe(on,{extractCertificationsFromDetails:()=>Ro,extractEducationFromDetails:()=>$o,extractExperienceFromDetails:()=>Eo,extractFeaturedFromDetails:()=>_o,extractRecommendationsFromDetails:()=>Do,extractSkillsFromDetails:()=>Io});async function Eo(e,t){let n=t.replace(/\/+$/,"")+"/details/experience/";try{await ce(e,n,{readySelector:"main .pvs-list, .scaffold-layout__main, main.scaffold-layout__main, section.artdeco-card",label:"experience details"}),await x(1e3);let o=e.url();if(!o.toLowerCase().includes("/details/experience/"))throw new Error(`Section /details/experience/ not available on profile (landed on ${o})`);for(let s=0;s<5;s++)await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await x(900);await x(1e3);let r=0;for(;r<5&&await e.evaluate(()=>{for(let i of document.querySelectorAll("button"))if(/^Load more$/i.test((i.textContent||"").trim()))return i.click(),!0;return!1});)r++,await x(1500),await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await x(700);return await e.evaluate(()=>{let s=document.querySelector("main");if(!s)return[];function i(c){let y=[];for(let u of c.querySelectorAll("div")){let m=Array.from(u.children).filter(b=>b.tagName==="DIV");if(m.length<3)continue;let C=m.filter(b=>(b.textContent||"").trim().length>50);C.length<3||(u.textContent||"").length<500||y.push({el:u,count:C.length,totalText:(u.textContent||"").length})}return y.sort((u,m)=>m.count-u.count||u.totalText-m.totalText),y[0]?.el}let a=i(s);if(!a)return[];let l=Array.from(a.children).filter(c=>c.tagName==="DIV").filter(c=>(c.textContent||"").trim().length>50);function d(c){let y=c[0]||null,u=null,m=null;if(c[1]){let v=c[1].split("\xB7").map(I=>I.trim());u=v[0]||null,v[1]&&(m=v[1])}let C=c.findIndex(v=>/(\d{4}|present)/i.test(v)&&/(-|–|—)/i.test(v)&&v.length<80),b=null,$=null;if(C>=0){let v=c[C].split("\xB7").map(I=>I.trim());b=v[0]||null,v[1]&&($=v[1])}let g=null,h=-1;if(C>=0&&c[C+1]){let v=c[C+1],I=/^[>*\-•]|^>/.test(v),le=/https?:\/\//.test(v),ae=/\d{4}/.test(v),R=/,/.test(v)||/\bArea\b|\bRegion\b|\bProvince\b|\bCountry\b|, [A-Z]{2}\b/.test(v);!I&&!le&&!ae&&v.length<100&&R&&(g=v,h=C+1)}let B=null,E=-1,Q=c[c.length-1];if(Q&&/and\s+\+\d+\s+skills?$/i.test(Q)){E=c.length-1;let v=Q.match(/^(.+?)\s+and\s+\+(\d+)\s+skills?$/i);v&&(B={listed:v[1].split(",").map(I=>I.trim()),additional:parseInt(v[2],10)})}let X=new Set([0,1,C,h,E].filter(v=>v>=0)),W=c.filter((v,I)=>!X.has(I)),O=W.length>0?W.join(`
|
|
4
4
|
|
|
5
|
-
`):null;return{title:
|
|
6
|
-
`).map(
|
|
7
|
-
${
|
|
5
|
+
`):null;return{title:y,company:u,employmentType:m,dates:b,duration:$,location:g,description:O,skills:B,_raw:c}}return l.map(c=>{let u=(c.innerText||"").trim().split(`
|
|
6
|
+
`).map(m=>m.trim()).filter(m=>m&&m.length<5e3);return d(u)}).filter(c=>c.title)})}catch(o){if(o.code==="SESSION_EXPIRED"||/^session expired/i.test(o.message||""))throw o;return console.error(`extractExperienceFromDetails failed for ${t}: ${o.message?.slice(0,120)}`),[]}}async function we(e,t,n){let o=t.replace(/\/+$/,"")+`/details/${n}/`;await ce(e,o,{readySelector:"main .pvs-list, .scaffold-layout__main, main.scaffold-layout__main, section.artdeco-card",label:`details/${n}`}),await x(1e3);let r=e.url();if(!r.toLowerCase().includes(`/details/${n.toLowerCase()}/`))throw new Error(`Section /details/${n}/ not available on profile (landed on ${r})`);for(let i=0;i<4;i++)await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await x(700);await x(800);let s=0;for(;s<5&&await e.evaluate(()=>{for(let a of document.querySelectorAll("button"))if(/^Load more$/i.test((a.textContent||"").trim()))return a.click(),!0;return!1});)s++,await x(1300),await e.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await x(600)}async function be(e,t,n,o){try{return await o()}catch(r){let s=r.message||"";if(r.code==="SESSION_EXPIRED"||/^session expired/i.test(s))throw r;return/not available on profile/i.test(s)?(console.log(` (${t}: section not on profile \u2014 skipped)`),[]):(console.error(`${n} failed for ${e}: ${s.slice(0,120)}`),[])}}async function _o(e,t){return be(t,"featured","extractFeaturedFromDetails",async()=>(await we(e,t,"featured"),await e.evaluate(`(() => {
|
|
7
|
+
${Se}
|
|
8
8
|
|
|
9
9
|
const main = document.querySelector('main');
|
|
10
10
|
if (!main) return [];
|
|
@@ -44,8 +44,8 @@ var Ln=Object.defineProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var me=(e,t)=>{f
|
|
|
44
44
|
if (e.caption && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.caption.trim())) return false;
|
|
45
45
|
return true;
|
|
46
46
|
});
|
|
47
|
-
})()`)))}async function
|
|
48
|
-
${
|
|
47
|
+
})()`)))}async function $o(e,t){return be(t,"education","extractEducationFromDetails",async()=>(await we(e,t,"education"),await e.evaluate(`(() => {
|
|
48
|
+
${Se}
|
|
49
49
|
|
|
50
50
|
const main = document.querySelector('main');
|
|
51
51
|
if (!main) return [];
|
|
@@ -81,8 +81,8 @@ var Ln=Object.defineProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var me=(e,t)=>{f
|
|
|
81
81
|
if (e.degree && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.degree.trim())) return false;
|
|
82
82
|
return true;
|
|
83
83
|
});
|
|
84
|
-
})()`)))}async function
|
|
85
|
-
${
|
|
84
|
+
})()`)))}async function Io(e,t){return be(t,"skills","extractSkillsFromDetails",async()=>(await we(e,t,"skills"),await e.evaluate(`(() => {
|
|
85
|
+
${Se}
|
|
86
86
|
|
|
87
87
|
const main = document.querySelector('main');
|
|
88
88
|
if (!main) return [];
|
|
@@ -113,8 +113,8 @@ var Ln=Object.defineProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var me=(e,t)=>{f
|
|
|
113
113
|
skills.push({ name, endorsements, _raw: lines });
|
|
114
114
|
}
|
|
115
115
|
return skills;
|
|
116
|
-
})()`)))}async function
|
|
117
|
-
${
|
|
116
|
+
})()`)))}async function Ro(e,t){return be(t,"certifications","extractCertificationsFromDetails",async()=>(await we(e,t,"certifications"),await e.evaluate(`(() => {
|
|
117
|
+
${Se}
|
|
118
118
|
|
|
119
119
|
const main = document.querySelector('main');
|
|
120
120
|
if (!main) return [];
|
|
@@ -138,8 +138,8 @@ var Ln=Object.defineProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var me=(e,t)=>{f
|
|
|
138
138
|
|
|
139
139
|
return { name, issuer, issueDate, expiryDate, credentialId, _raw: lines };
|
|
140
140
|
}).filter(e => e.name);
|
|
141
|
-
})()`)))}async function
|
|
142
|
-
${
|
|
141
|
+
})()`)))}async function Do(e,t){return be(t,"recommendations","extractRecommendationsFromDetails",async()=>(await we(e,t,"recommendations"),await e.evaluate(`(() => {
|
|
142
|
+
${Se}
|
|
143
143
|
|
|
144
144
|
const main = document.querySelector('main');
|
|
145
145
|
if (!main) return [];
|
|
@@ -174,7 +174,7 @@ var Ln=Object.defineProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var me=(e,t)=>{f
|
|
|
174
174
|
if (e.recommenderTitle && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.recommenderTitle.trim())) return false;
|
|
175
175
|
return true;
|
|
176
176
|
});
|
|
177
|
-
})()`)))}var
|
|
177
|
+
})()`)))}var Se,rn=Y(()=>{he();Se=`
|
|
178
178
|
function findEntryContainer(root) {
|
|
179
179
|
const candidates = [];
|
|
180
180
|
for (const div of root.querySelectorAll('div')) {
|
|
@@ -237,60 +237,60 @@ function findEntryContainer(root) {
|
|
|
237
237
|
candidates.sort((a, b) => b.count - a.count || b.totalText - a.totalText);
|
|
238
238
|
return candidates[0]?.el;
|
|
239
239
|
}
|
|
240
|
-
`});var nn=z(()=>{});import"dotenv/config";import*as j from"fs";import*as D from"path";import{fileURLToPath as $o}from"url";function To(){j.existsSync(ee)||j.mkdirSync(ee,{recursive:!0})}function Oo(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";j.writeFileSync(o,n);for(let r=0;r<3;r++)try{j.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 Je(e){try{if(j.existsSync(e))return JSON.parse(j.readFileSync(e,"utf-8"))}catch{}return null}function rn(){Se=null,No=null,ve=null,Lo=null,Z=null}function jo(){return Se||(Se=Je(Ro)||{},Se)}function Ie(){return ve||(ve=Je(Do)||{},ve)}function ce(){return Z||(Z=Je(on)||{visits:[]},Array.isArray(Z.visits)||(Z.visits=[]),Z)}function sn(e,t={}){if(!e)return;To();let n=ce();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{Oo(on,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function ln(e){let t=ce();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function an(){let e=jo(),t=Ie(),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,m)=>m[1]-u[1]).slice(0,5).map(([u,m])=>`${u} (${m})`),i={};for(let u of n)for(let m of u.techStack||[])i[m]=(i[m]||0)+1;let c=Object.entries(i).sort((u,m)=>m[1]-u[1]).slice(0,8).map(([u,m])=>`${u} (${m})`),l=ce(),a=new Date().toISOString().slice(0,10),d=l.visits.filter(u=>u.date.startsWith(a)).length,p=new Set(l.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:c,totalVisited:p,todayVisited:d,lastScoutDate:n.length>0?n.sort((u,m)=>(m.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}var _o,Io,ee,Ro,Do,on,js,Ns,Se,No,ve,Lo,Z,Re=z(()=>{nn();_o=$o(import.meta.url),Io=D.dirname(D.dirname(_o)),ee=process.env.DATA_DIR?D.resolve(process.env.DATA_DIR,"market-research"):D.join(Io,"market-research"),Ro=D.join(ee,"scouted-jobs.json"),Do=D.join(ee,"discovered-people.json"),on=D.join(ee,"visit-log.json"),js=D.join(ee,"companies.json"),Ns=D.join(ee,"job-decisions.json");Se=null;No=null,ve=null;Lo=null,Z=null});import eo from"puppeteer";import{addExtra as to}from"puppeteer-extra";import no from"puppeteer-extra-plugin-stealth";pe();await Pe();Ae();Ae();import Me from"fs";import Yn from"path";var Kn={headless:!1,defaultViewport:null,protocolTimeout:3e5},ge=new Set,Tt=!1;async function $e(){let e=[...ge].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ge.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function zn(){if(Tt)return;Tt=!0;let e=t=>async()=>{await $e(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await $e(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await $e(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await $e(),process.exit(0))}),process.on("exit",()=>{for(let t of ge)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function qe(e){return Yn.join(e,".ilml-browser-session.json")}function Lt(e,t={}){try{Me.writeFileSync(qe(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function Qn(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();Lt(e,n?{wsEndpoint:n}:{})}catch{}}function Xn(e){let t=Ft(e);t&&t.ownerPid===process.pid&&he(e)}function he(e){try{Me.rmSync(qe(e),{force:!0})}catch{}}function Ft(e){try{return JSON.parse(Me.readFileSync(qe(e),"utf-8"))}catch{return null}}function Ot(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function jt(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Nt(e,t,{probeTimeoutMs:n=4e3}={}){let o=Ft(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Ot(o.ownerPid)?"live":(he(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await jt(s,n,r);if(i===r)return s.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return he(t),"free";if(Ot(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await jt(i.close().then(()=>!0).catch(()=>!1),n,!1))return he(t),"free";try{i.disconnect()}catch{}return"live"}function Zn(e,t){e&&(zn(),ge.add(e),e.on("disconnected",()=>{ge.delete(e),t&&he(t)}))}async function Pt(e,t={}){let n=At(),o={...Kn,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. 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 Nt(e,n)==="live")throw new Error(r);Lt(n);let s;try{s=await e.launch(o)}catch{if(await Nt(e,n)==="live")throw new Error(r);It(n);try{s=await e.launch(o)}catch(c){throw Xn(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${c.message})`)}}return Zn(s,n),Qn(n,s),{browser:s,profileDir:n,needsSeed:$t(n)}}async function Mt(e,t){if(!t||!t.needsSeed)return!1;if(!Ct())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(...kt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var qt=to(eo);qt.use(no());async function Ut(){let{browser:e,profileDir:t,needsSeed:n}=await Pt(qt),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Mt(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}pe();await _e();async function Eo(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: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await C(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function tn(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:c=!1,extractCertifications:l=!1}=n;try{await Fe(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await C(1500),await Dt(e)&&(console.log(" Dismissed Premium overlay"),await C(1500),await Eo(e,t));let d=0;for(let x=0;x<2;x++){let ue=await e.evaluate(()=>document.body.scrollHeight);for(let K=1;K<=6;K++)await e.evaluate(w=>window.scrollTo(0,w),ue*K/6),await C(900);let J=await e.evaluate(()=>document.body.scrollHeight);if(J===d)break;d=J}await C(800);let p=null;try{let x=e.url();x&&/\/in\/[^/?#]/.test(x)&&(p=x.split("?")[0].replace(/\/+$/,""))}catch{}let u=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"]),ue=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"]),J=h=>{let b=(h||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(b)},K={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:[]},w=document.querySelector("main");if(!w)return K;let T=[...Array.from(w.querySelectorAll("h1")),...Array.from(w.querySelectorAll("h2"))],de=Array.from(w.querySelectorAll("h2")),se=T.find(h=>{let b=h.textContent&&h.textContent.trim(),S=(b||"").replace(/\s*\(\d+\)$/,"").trim();return b&&b.length>1&&b.length<100&&!x.has(S)&&!ue.has(S)}),ie=se?se.textContent.trim():null,O=se?se.parentElement:null;for(;O&&O!==w&&!Array.from(O.children).filter(b=>b.tagName==="P").some(b=>{let S=b.textContent&&b.textContent.trim();return S&&S.length>5&&!J(S)});)O=O.parentElement;let lt=null,at=null,ct=null,je=null;if(O&&O!==w){let h=Array.from(O.children).filter(S=>S.tagName==="P").filter(S=>{let W=S.textContent&&S.textContent.trim();return W&&W.length>5&&!J(W)});if(h[0]&&(lt=h[0].textContent.trim()),h[1]){let S=h[1].textContent.trim().split(/\s*[·•]\s*/);at=S[0]||null,S.length>1&&(ct=S.slice(1).join(" \xB7 "))}let b=Array.from(O.children).filter(S=>S.tagName==="DIV");for(let S=b.length-1;S>=0;S--){let W=b[S].textContent&&b[S].textContent.trim();if(W&&!(ie&&W.startsWith(ie))&&(je=W.split(/[·•]/)[0].trim(),je))break}}let ut=null;if(O){let h=O.querySelectorAll("p, span, div");for(let b of h){let S=(b.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){ut=S;break}}}let In=!!w.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),dt=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Rn=dt&&dt.src||null,Ce=w.textContent||"",ft=null,mt=Ce.match(/(\d+\+?)\s+connections\b/i);mt&&(ft=mt[1]+" connections");let xe=null,pt=Ce.match(/(\d+)\s+mutual connection/i),ht=Ce.match(/and\s+(\d+)\s+other mutual/i),Dn=Ce.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);pt?xe=parseInt(pt[1],10):ht?xe=2+parseInt(ht[1],10):Dn&&(xe=2);let gt=null,yt=null,wt=de.find(h=>h.textContent&&h.textContent.trim()==="About");if(wt){let h=wt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let b=(h.textContent||"").trim();b=b.replace(/^About\s*/,"").trim();let S=b.match(/Top skills\s*(.+?)$/i);if(S){let Ne=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Nn=>Nn.trim()).filter(Boolean);Ne.length>0&&Ne.length<10&&(yt=Ne)}b=b.split(/\n?…\s*more/i)[0].trim(),b=b.split(/\n?\.\.\.\s*more/i)[0].trim(),b=b.split(/\bTop skills\b/i)[0].trim(),b=b.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),gt=b||null}}let bt=null,St=de.find(h=>h.textContent&&h.textContent.trim()==="Activity");if(St){let h=St.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let b=(h.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);b&&(bt=parseInt(b[1].replace(/,/g,""),10))}}let Tn=[...new Set(de.map(h=>(h.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(h=>h&&h.length>1&&h.length<60&&h!==ie).filter(h=>x.has(h)))],fe=document.body.textContent||"",On=/#OpenToWork\b/i.test(fe)||/\bOpen to work\b/.test(fe),jn=/#Hiring\b/i.test(fe)||/\bI'?m hiring\b/i.test(fe)||/\bWe are hiring\b/i.test(fe);return{name:ie,title:lt,location:je,currentCompany:at,currentSchool:ct,connectionDegree:ut,hasVerifiedBadge:In,photoUrl:Rn,connectionsLevel:ft,mutualConnectionsCount:xe,followers:bt,openToWork:On,hiring:jn,about:gt,topSkills:yt,availableSections:Tn}});console.log(`Viewed: ${u.name} - ${u.title}`);let m=null,E=null,k=null,_=null,y=null,g=null;if(o||r||s||i||c||l){let x=await Promise.resolve().then(()=>(en(),Zt));o&&(m=await x.extractExperienceFromDetails(e,t),console.log(` + Experience: ${m.length} entries`)),r&&(E=await x.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${E.length} entries`)),s&&(k=await x.extractEducationFromDetails(e,t),console.log(` + Education: ${k.length} entries`)),i&&(_=await x.extractSkillsFromDetails(e,t),console.log(` + Skills: ${_.length} entries`)),c&&(y=await x.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${y.length} entries`)),l&&(g=await x.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${g.length} entries`))}return{...u,profileUrl:t,resolvedUrl:p,...m!==null&&{experience:m},...E!==null&&{featured:E},...k!==null&&{education:k},..._!==null&&{skillsFull:_},...y!==null&&{recommendations:y},...g!==null&&{certifications:g}}}catch(a){console.error(`Error viewing profile ${t}:`,a.message);try{await e.goto("about:blank",{timeout:1e4}),await C(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(a.message||""))throw a;return null}}Re();await _e();pe();async function cn(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:c=!1}=n,l={visited:0,skipped:0,errors:0},a=Date.now()-s*24*60*60*1e3;for(let d of t){if(l.visited>=o)break;if(!d.profileUrl)continue;let p=ln(d.profileUrl);if(p&&new Date(p).getTime()>a){l.skipped++;continue}try{let u=await tn(e,d.profileUrl,{extractExperience:c}),m=d.source||"visit";if(u){let k={...u,source:m};c&&Array.isArray(u.experience)&&u.experience.length>0&&(k._lastEnrichedAt=new Date().toISOString()),Ve(d.profileUrl,k),u.resolvedUrl&&He(d.profileUrl,u.resolvedUrl)}sn(d.profileUrl,{name:u?.name||d.name||null,source:m}),l.visited++,i?console.log(` [${l.visited}] ${u?.name||d.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):l.visited%10===0&&console.log(` Visited: ${l.visited}`);let E=Math.random()*4e3;await C(r+E)}catch(u){l.errors++,i&&console.error(` Error visiting ${d.name||d.profileUrl}: ${u.message?.slice(0,60)}`);let m=(u.message||"").toLowerCase();if(m.includes("detached")||m.includes("session closed")||m.includes("target closed")||m.includes("session expired")||m.includes("bounced to")||m.includes("authwall")||m.includes("checkpoint")||m.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return l}function un(e={}){let{limit:t=150,revisitDays:n=14}=e,o=ce(),r=new Set(o.visits?.map(y=>y.profileUrl)||[]),s=new Map;for(let y of o.visits||[]){let g=s.get(y.profileUrl);(!g||y.date>g)&&s.set(y.profileUrl,y.date)}let i=Date.now()-7*24*60*60*1e3,c=Date.now()-n*24*60*60*1e3;function l(y){let g=s.get(y);return g&&new Date(g).getTime()>i}let a=Ie(),d=Object.entries(a).filter(([y])=>!r.has(y)).map(([y,g])=>({profileUrl:y,name:g.name,source:"new-discovered",priority:1,jobCount:g.jobIds?.length||0})),p=ae(),u=Object.entries(p).filter(([y,g])=>g.connectionStatus==="connected"||l(y)?!1:(g.jobCount||g.jobIds?.length||0)>=2).map(([y,g])=>({profileUrl:y,name:g.name,source:"active-recruiter",priority:2,jobCount:g.jobCount||g.jobIds?.length||0})),m=Object.entries(p).filter(([y,g])=>{let U=g.connectionStatus;return!(!U||U==="connected"||U==="pending"||U==="sent_without_note"||U==="sent_with_note"||l(y)||(g.jobCount||g.jobIds?.length||0)>=2)}).map(([y,g])=>({profileUrl:y,name:g.name,source:"unconnected",priority:3,jobCount:g.jobCount||g.jobIds?.length||0})),E=[...s.entries()].filter(([y,g])=>new Date(g).getTime()<c).map(([y,g])=>({profileUrl:y,name:null,source:"revisit",priority:4,lastVisit:g})),k=y=>y.sort(()=>Math.random()-.5);return d.sort((y,g)=>g.jobCount-y.jobCount),u.sort((y,g)=>g.jobCount-y.jobCount),k(m),k(E),[...d,...u,...m,...E].slice(0,t)}function dn(){let e=Ie();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function We(){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 Ge(e=14){let t=ce(),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"}))}Re();import"dotenv/config";import*as N from"fs";import*as G from"path";import{fileURLToPath as Bo}from"url";import"dotenv/config";import{createGraphClient as Fo,resolveToken as Po}from"@ilivemylife/graph-sdk";import{config as Mo}from"dotenv";Mo();var fn=Po()||process.env.TOKEN;fn||(console.error(`
|
|
240
|
+
`});var ln=Y(()=>{});import"dotenv/config";import*as N from"fs";import*as j from"path";import{fileURLToPath as Oo}from"url";function Fo(){N.existsSync(ne)||N.mkdirSync(ne,{recursive:!0})}function Mo(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 He(e){try{if(N.existsSync(e))return JSON.parse(N.readFileSync(e,"utf-8"))}catch{}return null}function cn(){ve=null,Uo=null,ke=null,Bo=null,te=null}function qo(){return ve||(ve=He(Lo)||{},ve)}function Ie(){return ke||(ke=He(Po)||{},ke)}function fe(){return te||(te=He(an)||{visits:[]},Array.isArray(te.visits)||(te.visits=[]),te)}function un(e,t={}){if(!e)return;Fo();let n=fe();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{Mo(an,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function dn(e){let t=fe();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function fn(){let e=qo(),t=Ie(),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,m)=>m[1]-u[1]).slice(0,5).map(([u,m])=>`${u} (${m})`),i={};for(let u of n)for(let m of u.techStack||[])i[m]=(i[m]||0)+1;let a=Object.entries(i).sort((u,m)=>m[1]-u[1]).slice(0,8).map(([u,m])=>`${u} (${m})`),l=fe(),d=new Date().toISOString().slice(0,10),c=l.visits.filter(u=>u.date.startsWith(d)).length,y=new Set(l.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:a,totalVisited:y,todayVisited:c,lastScoutDate:n.length>0?n.sort((u,m)=>(m.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}var jo,No,ne,Lo,Po,an,Bs,Vs,ve,Uo,ke,Bo,te,Re=Y(()=>{ln();jo=Oo(import.meta.url),No=j.dirname(j.dirname(jo)),ne=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(No,"market-research"),Lo=j.join(ne,"scouted-jobs.json"),Po=j.join(ne,"discovered-people.json"),an=j.join(ne,"visit-log.json"),Bs=j.join(ne,"companies.json"),Vs=j.join(ne,"job-decisions.json");ve=null;Uo=null,ke=null;Bo=null,te=null});import io from"puppeteer";import{addExtra as lo}from"puppeteer-extra";import ao from"puppeteer-extra-plugin-stealth";he();await Pe();Ee();Ee();import Fe from"fs";import eo from"path";var to={headless:!1,defaultViewport:null,protocolTimeout:3e5},ye=new Set,Lt=!1;async function _e(){let e=[...ye].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ye.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function no(){if(Lt)return;Lt=!0;let e=t=>async()=>{await _e(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await _e(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await _e(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await _e(),process.exit(0))}),process.on("exit",()=>{for(let t of ye)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function Me(e){return eo.join(e,".ilml-browser-session.json")}function qt(e,t={}){try{Fe.writeFileSync(Me(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function oo(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();qt(e,n?{wsEndpoint:n}:{})}catch{}}function ro(e){let t=Ut(e);t&&t.ownerPid===process.pid&&ge(e)}function ge(e){try{Fe.rmSync(Me(e),{force:!0})}catch{}}function Ut(e){try{return JSON.parse(Fe.readFileSync(Me(e),"utf-8"))}catch{return null}}function Pt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Ft(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Mt(e,t,{probeTimeoutMs:n=4e3}={}){let o=Ut(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Pt(o.ownerPid)?"live":(ge(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Ft(s,n,r);if(i===r)return s.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return ge(t),"free";if(Pt(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Ft(i.close().then(()=>!0).catch(()=>!1),n,!1))return ge(t),"free";try{i.disconnect()}catch{}return"live"}function so(e,t){e&&(no(),ye.add(e),e.on("disconnected",()=>{ye.delete(e),t&&ge(t)}))}async function Bt(e,t={}){let n=xt(),o={...to,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. 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 Mt(e,n)==="live")throw new Error(r);qt(n);let s;try{s=await e.launch(o)}catch{if(await Mt(e,n)==="live")throw new Error(r);$t(n);try{s=await e.launch(o)}catch(a){throw ro(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${a.message})`)}}return so(s,n),oo(n,s),{browser:s,profileDir:n,needsSeed:Et(n)}}async function Vt(e,t){if(!t||!t.needsSeed)return!1;if(!kt())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(...vt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Ht=lo(io);Ht.use(ao());async function Jt(){let{browser:e,profileDir:t,needsSeed:n}=await Bt(Ht),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Vt(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}he();await $e();async function To(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: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await x(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function sn(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:a=!1,extractCertifications:l=!1}=n;try{await ce(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await x(1500),await Dt(e)&&(console.log(" Dismissed Premium overlay"),await x(1500),await To(e,t));let c=0;for(let E=0;E<2;E++){let Q=await e.evaluate(()=>document.body.scrollHeight);for(let W=1;W<=6;W++)await e.evaluate(O=>window.scrollTo(0,O),Q*W/6),await x(900);let X=await e.evaluate(()=>document.body.scrollHeight);if(X===c)break;c=X}await x(800);let y=null;try{let E=e.url();E&&/\/in\/[^/?#]/.test(E)&&(y=E.split("?")[0].replace(/\/+$/,""))}catch{}let u=await e.evaluate(()=>{let E=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"]),Q=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"]),X=p=>{let w=(p||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(w)},W={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:[]},O=document.querySelector("main");if(!O)return W;let v=[...Array.from(O.querySelectorAll("h1")),...Array.from(O.querySelectorAll("h2"))],I=Array.from(O.querySelectorAll("h2")),le=v.find(p=>{let w=p.textContent&&p.textContent.trim(),S=(w||"").replace(/\s*\(\d+\)$/,"").trim();return w&&w.length>1&&w.length<100&&!E.has(S)&&!Q.has(S)}),ae=le?le.textContent.trim():null,R=le?le.parentElement:null;for(;R&&R!==O&&!Array.from(R.children).filter(w=>w.tagName==="P").some(w=>{let S=w.textContent&&w.textContent.trim();return S&&S.length>5&&!X(S)});)R=R.parentElement;let it=null,lt=null,at=null,je=null;if(R&&R!==O){let p=Array.from(R.children).filter(S=>S.tagName==="P").filter(S=>{let G=S.textContent&&S.textContent.trim();return G&&G.length>5&&!X(G)});if(p[0]&&(it=p[0].textContent.trim()),p[1]){let S=p[1].textContent.trim().split(/\s*[·•]\s*/);lt=S[0]||null,S.length>1&&(at=S.slice(1).join(" \xB7 "))}let w=Array.from(R.children).filter(S=>S.tagName==="DIV");for(let S=w.length-1;S>=0;S--){let G=w[S].textContent&&w[S].textContent.trim();if(G&&!(ae&&G.startsWith(ae))&&(je=G.split(/[·•]/)[0].trim(),je))break}}let ct=null;if(R){let p=R.querySelectorAll("p, span, div");for(let w of p){let S=(w.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){ct=S;break}}}let On=!!O.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ut=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),jn=ut&&ut.src||null,xe=O.textContent||"",dt=null,ft=xe.match(/(\d+\+?)\s+connections\b/i);ft&&(dt=ft[1]+" connections");let Ae=null,mt=xe.match(/(\d+)\s+mutual connection/i),pt=xe.match(/and\s+(\d+)\s+other mutual/i),Nn=xe.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);mt?Ae=parseInt(mt[1],10):pt?Ae=2+parseInt(pt[1],10):Nn&&(Ae=2);let ht=null,gt=null,yt=I.find(p=>p.textContent&&p.textContent.trim()==="About");if(yt){let p=yt.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 Ne=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Mn=>Mn.trim()).filter(Boolean);Ne.length>0&&Ne.length<10&&(gt=Ne)}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(),ht=w||null}}let wt=null,St=I.find(p=>p.textContent&&p.textContent.trim()==="Activity");if(St){let p=St.parentElement;for(;p&&p.tagName!=="SECTION";)p=p.parentElement;if(p){let w=(p.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);w&&(wt=parseInt(w[1].replace(/,/g,""),10))}}let Ln=[...new Set(I.map(p=>(p.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(p=>p&&p.length>1&&p.length<60&&p!==ae).filter(p=>E.has(p)))],me=document.body.textContent||"",Pn=/#OpenToWork\b/i.test(me)||/\bOpen to work\b/.test(me),Fn=/#Hiring\b/i.test(me)||/\bI'?m hiring\b/i.test(me)||/\bWe are hiring\b/i.test(me);return{name:ae,title:it,location:je,currentCompany:lt,currentSchool:at,connectionDegree:ct,hasVerifiedBadge:On,photoUrl:jn,connectionsLevel:dt,mutualConnectionsCount:Ae,followers:wt,openToWork:Pn,hiring:Fn,about:ht,topSkills:gt,availableSections:Ln}});console.log(`Viewed: ${u.name} - ${u.title}`);let m=null,C=null,b=null,$=null,g=null,h=null;if(o||r||s||i||a||l){let E=await Promise.resolve().then(()=>(rn(),on));o&&(m=await E.extractExperienceFromDetails(e,t),console.log(` + Experience: ${m.length} entries`)),r&&(C=await E.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${C.length} entries`)),s&&(b=await E.extractEducationFromDetails(e,t),console.log(` + Education: ${b.length} entries`)),i&&($=await E.extractSkillsFromDetails(e,t),console.log(` + Skills: ${$.length} entries`)),a&&(g=await E.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${g.length} entries`)),l&&(h=await E.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${h.length} entries`))}return{...u,profileUrl:t,resolvedUrl:y,...m!==null&&{experience:m},...C!==null&&{featured:C},...b!==null&&{education:b},...$!==null&&{skillsFull:$},...g!==null&&{recommendations:g},...h!==null&&{certifications:h}}}catch(d){console.error(`Error viewing profile ${t}:`,d.message);try{await e.goto("about:blank",{timeout:1e4}),await x(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(d.message||""))throw d;return null}}Re();await $e();he();async function mn(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:a=!1}=n,l={visited:0,skipped:0,errors:0},d=Date.now()-s*24*60*60*1e3;for(let c of t){if(l.visited>=o)break;if(!c.profileUrl)continue;let y=dn(c.profileUrl);if(y&&new Date(y).getTime()>d){l.skipped++;continue}try{let u=await sn(e,c.profileUrl,{extractExperience:a}),m=c.source||"visit";if(u){let b={...u,source:m};a&&Array.isArray(u.experience)&&u.experience.length>0&&(b._lastEnrichedAt=new Date().toISOString()),Be(c.profileUrl,b),u.resolvedUrl&&Ve(c.profileUrl,u.resolvedUrl)}un(c.profileUrl,{name:u?.name||c.name||null,source:m}),l.visited++,i?console.log(` [${l.visited}] ${u?.name||c.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):l.visited%10===0&&console.log(` Visited: ${l.visited}`);let C=Math.random()*4e3;await x(r+C)}catch(u){l.errors++,i&&console.error(` Error visiting ${c.name||c.profileUrl}: ${u.message?.slice(0,60)}`);let m=(u.message||"").toLowerCase();if(m.includes("detached")||m.includes("session closed")||m.includes("target closed")||m.includes("session expired")||m.includes("bounced to")||m.includes("authwall")||m.includes("checkpoint")||m.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return l}function pn(e={}){let{limit:t=150,revisitDays:n=14}=e,o=fe(),r=new Set(o.visits?.map(g=>g.profileUrl)||[]),s=new Map;for(let g of o.visits||[]){let h=s.get(g.profileUrl);(!h||g.date>h)&&s.set(g.profileUrl,g.date)}let i=Date.now()-7*24*60*60*1e3,a=Date.now()-n*24*60*60*1e3;function l(g){let h=s.get(g);return h&&new Date(h).getTime()>i}let d=Ie(),c=Object.entries(d).filter(([g])=>!r.has(g)).map(([g,h])=>({profileUrl:g,name:h.name,source:"new-discovered",priority:1,jobCount:h.jobIds?.length||0})),y=de(),u=Object.entries(y).filter(([g,h])=>h.connectionStatus==="connected"||l(g)?!1:(h.jobCount||h.jobIds?.length||0)>=2).map(([g,h])=>({profileUrl:g,name:h.name,source:"active-recruiter",priority:2,jobCount:h.jobCount||h.jobIds?.length||0})),m=Object.entries(y).filter(([g,h])=>{let B=h.connectionStatus;return!(!B||B==="connected"||B==="pending"||B==="sent_without_note"||B==="sent_with_note"||l(g)||(h.jobCount||h.jobIds?.length||0)>=2)}).map(([g,h])=>({profileUrl:g,name:h.name,source:"unconnected",priority:3,jobCount:h.jobCount||h.jobIds?.length||0})),C=[...s.entries()].filter(([g,h])=>new Date(h).getTime()<a).map(([g,h])=>({profileUrl:g,name:null,source:"revisit",priority:4,lastVisit:h})),b=g=>g.sort(()=>Math.random()-.5);return c.sort((g,h)=>h.jobCount-g.jobCount),u.sort((g,h)=>h.jobCount-g.jobCount),b(m),b(C),[...c,...u,...m,...C].slice(0,t)}function hn(){let e=Ie();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function Je(){let e=de();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 We(e=14){let t=fe(),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"}))}Re();import"dotenv/config";import*as L from"fs";import*as K from"path";import{fileURLToPath as Yo}from"url";import"dotenv/config";import{createGraphClient as Vo,resolveToken as Ho}from"@ilivemylife/graph-sdk";import{config as Jo}from"dotenv";Jo();var gn=Ho()||process.env.TOKEN;gn||(console.error(`
|
|
241
241
|
[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.
|
|
242
242
|
`),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
|
|
243
243
|
`),console.error(` Or add ILML_TOKEN=... to .env
|
|
244
|
-
`),process.exit(1));var
|
|
244
|
+
`),process.exit(1));var yn=Vo({token:gn});function Wo(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 Go({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"," "),a=r?` ${Wo(r)}`:"",l=n==="error"?`error${o?`: ${String(o).slice(0,80)}`:""}`:n==="quota-reached"?"daily quota reached":"done",d=t&&t!=="default"?` (${t})`:"";return`${s} ${e}${d} \u2014 ${l} \u2014 ${i}${a}`}async function wn({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 l=`**${Go({scriptName:e,mode:t,status:n,error:r,duration:s})}**
|
|
245
245
|
|
|
246
246
|
${o.join(`
|
|
247
|
-
`)}`;try{await mn.addMessage(i,l)}catch(a){console.error(`[runReport] Failed to post end-of-session message: ${a.message}`)}}var Vo=Bo(import.meta.url),Ho=G.dirname(G.dirname(Vo)),Ye=process.env.DATA_DIR?G.resolve(process.env.DATA_DIR,"collected-profiles"):G.join(Ho,"collected-profiles"),Ke=G.join(Ye,"run-log.json");function Jo(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 hn(){try{if(!N.existsSync(Ke))return{runs:[]};let e=JSON.parse(N.readFileSync(Ke,"utf-8"));return!e.runs||!Array.isArray(e.runs)?{runs:[]}:e}catch{return{runs:[]}}}function gn(e){N.existsSync(Ye)||N.mkdirSync(Ye,{recursive:!0});try{Jo(Ke,e)}catch(t){console.error(`[!] Failed to save run-log.json: ${t.message}`)}}function yn(e,t="default"){let n=hn();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),gn(n),{script:e,mode:t,startTime:o.getTime(),runIndex:n.runs.length-1}}function ze(e,t,n=null,o=null){if(!e)return;let r=null;try{let i=hn(),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 l=i.runs.find(a=>a.script===e.script&&a.status==="running"&&a.timestamp===new Date(e.startTime).toISOString());l&&(l.status=t,l.result=t,l.duration=Date.now()-e.startTime,l.stats=n,l.error=o,r=l.duration)}gn(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";pn({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 f from"fs";import*as rt from"os";import*as v from"path";import{fileURLToPath as hr}from"url";var Ze={};me(Ze,{NAME:()=>Go,VERSION:()=>Wo,up:()=>zo});import*as L from"fs";import*as De from"path";var Wo=1,Go="normalize-jobs-and-questions",Yo=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],Ko=50;function Qe(e){return L.existsSync(e)?JSON.parse(L.readFileSync(e,"utf-8")):null}function Xe(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 zo(e,t){let n=De.join(e,"jobs.json"),o=De.join(t,"scouted-jobs.json"),r=De.join(e,"application-questions.json"),s=Qe(n)||{},i=Qe(o)||{},c=0,l=[];for(let d of Object.keys(s)){if(!i[d])continue;let p=i[d],u=s[d];for(let m of Yo)p[m]!=null&&u[m]==null&&(u[m]=p[m]);l.push(d),c++}c>0?(Xe(n,s),console.log(` Phase 1: Merged ${c} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let a=Qe(r);if(a&&Array.isArray(a.applications)){let d={},p=0;for(let u of a.applications){let m=u.jobId||"unknown";d[m]||(d[m]={applications:[]});let E=(u.questions||[]).map(k=>{let _={type:k.type||"input",question:k.question||"",answer:k.answer||""};return k.options&&(k.options.length<=Ko?_.options=k.options:(_.optionsOmitted=!0,p++)),k.page&&(_.page=k.page),k.wasPreFilled&&(_.wasPreFilled=!0),k.wasRetry&&(_.wasRetry=!0),_});d[m].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||E.length,questions:E})}Xe(r,d),console.log(` Phase 2: Rekeyed ${a.applications.length} applications by jobId (${Object.keys(d).length} unique jobs)`),p>0&&console.log(` Phase 2: Stripped bloated options from ${p} questions`)}else a&&!a.applications?console.log(" Phase 2: application-questions.json already in keyed format"):console.log(" Phase 2: No application-questions.json found");if(l.length>0){for(let d of l)delete i[d];Xe(o,i),console.log(` Phase 3: Removed ${l.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 et={};me(et,{NAME:()=>Xo,VERSION:()=>Qo,up:()=>tr});import*as F from"fs";import*as wn from"path";var Qo=2,Xo="reset-unread-flags";function Zo(e){return F.existsSync(e)?JSON.parse(F.readFileSync(e,"utf-8")):null}function er(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 tr(e){let t=wn.join(e,"conversations.json"),n=Zo(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}er(t,n),console.log(` Reset unread=false on ${o} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var tt={};me(tt,{NAME:()=>or,VERSION:()=>nr,up:()=>ar});import*as P from"fs";import*as bn from"path";var nr=3,or="recompute-last-message-by",rr=new Set(["draft","confirmed","rejected","needs_review"]);function sr(e){return P.existsSync(e)?JSON.parse(P.readFileSync(e,"utf-8")):null}function ir(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}}function lr(e,t){if(!e||e.length===0)return"them";let n=e.filter(s=>!rr.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 ar(e){let t=bn.join(e,"conversations.json"),n=sr(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 l=n[c];if(!l||typeof l!="object")continue;Object.prototype.hasOwnProperty.call(l,"_cardSynced")&&(delete l._cardSynced,s++);let a=Array.isArray(l.messages)?l.messages:[],d=lr(a,o),p=d==="me"?"awaiting_reply":"unanswered_by_us";l.lastMessageBy!==d&&i++,l.lastMessageBy=d,l.conversationStatus=p,r++}if(r===0){console.log(" No conversations to recompute.");return}ir(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 nt={};me(nt,{NAME:()=>ur,VERSION:()=>cr,up:()=>pr});import*as M from"fs";import*as Sn from"path";var cr=4,ur="split-connection-timestamps",dr=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function fr(e){if(!M.existsSync(e))return null;let t=M.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function mr(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}}async function pr(e){let t=Sn.join(e,"people.json"),n=fr(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++,dr.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}mr(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".')}await _e();Re();var gr=hr(import.meta.url),yr=v.dirname(gr),wr=v.dirname(v.dirname(yr));function br(e){return e&&(e==="~"?rt.homedir():e.startsWith("~/")||e.startsWith("~\\")?v.join(rt.homedir(),e.slice(2)):e)}var q=process.env.DATA_DIR?v.resolve(br(process.env.DATA_DIR)):wr,ne=v.join(q,"collected-profiles"),oe=v.join(q,"market-research"),R=v.join(q,".schema-version"),vn=[v.join(ne,".schema-version"),v.join(oe,".schema-version")],Y=4,H=[{version:1,module:Ze},{version:2,module:et},{version:3,module:tt},{version:4,module:nt}];(function(){H.sort((o,r)=>o.version-r.version);let t=new Set;for(let o of H){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=H.length?H[H.length-1].version:0;if(n!==Y)throw new Error(`MIGRATION_REGISTRY: highest version v${n} but CURRENT_SCHEMA_VERSION=${Y}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let o=0;o<H.length;o++){let r=o+1;if(H[o].version!==r)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${r} at position ${o} but found v${H[o].version}`)}})();function An(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
|
|
247
|
+
`)}`;try{await yn.addMessage(i,l)}catch(d){console.error(`[runReport] Failed to post end-of-session message: ${d.message}`)}}var Ko=Yo(import.meta.url),zo=K.dirname(K.dirname(Ko)),Ge=process.env.DATA_DIR?K.resolve(process.env.DATA_DIR,"collected-profiles"):K.join(zo,"collected-profiles"),Ye=K.join(Ge,"run-log.json");function Qo(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";L.writeFileSync(o,n);for(let r=0;r<3;r++)try{L.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 Sn(){try{if(!L.existsSync(Ye))return{runs:[]};let e=JSON.parse(L.readFileSync(Ye,"utf-8"));return!e.runs||!Array.isArray(e.runs)?{runs:[]}:e}catch{return{runs:[]}}}function bn(e){L.existsSync(Ge)||L.mkdirSync(Ge,{recursive:!0});try{Qo(Ye,e)}catch(t){console.error(`[!] Failed to save run-log.json: ${t.message}`)}}function vn(e,t="default"){let n=Sn();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),bn(n),{script:e,mode:t,startTime:o.getTime(),runIndex:n.runs.length-1}}function Ke(e,t,n=null,o=null){if(!e)return;let r=null;try{let i=Sn(),a=i.runs[e.runIndex];if(a&&a.status==="running"&&a.script===e.script)a.status=t,a.result=t,a.duration=Date.now()-e.startTime,a.stats=n,a.error=o,r=a.duration;else{let l=i.runs.find(d=>d.script===e.script&&d.status==="running"&&d.timestamp===new Date(e.startTime).toISOString());l&&(l.status=t,l.result=t,l.duration=Date.now()-e.startTime,l.stats=n,l.error=o,r=l.duration)}bn(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";wn({scriptName:e.script,mode:e.mode,status:i,summaryLines:s,error:o,duration:r??Date.now()-e.startTime}).catch(a=>console.error(`[runLog] postRunReport failed: ${a.message}`))}}import"dotenv/config";import*as f from"fs";import*as ot from"os";import*as k from"path";import{fileURLToPath as vr}from"url";var Xe={};pe(Xe,{NAME:()=>Zo,VERSION:()=>Xo,up:()=>nr});import*as P from"fs";import*as De from"path";var Xo=1,Zo="normalize-jobs-and-questions",er=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],tr=50;function ze(e){return P.existsSync(e)?JSON.parse(P.readFileSync(e,"utf-8")):null}function Qe(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 nr(e,t){let n=De.join(e,"jobs.json"),o=De.join(t,"scouted-jobs.json"),r=De.join(e,"application-questions.json"),s=ze(n)||{},i=ze(o)||{},a=0,l=[];for(let c of Object.keys(s)){if(!i[c])continue;let y=i[c],u=s[c];for(let m of er)y[m]!=null&&u[m]==null&&(u[m]=y[m]);l.push(c),a++}a>0?(Qe(n,s),console.log(` Phase 1: Merged ${a} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let d=ze(r);if(d&&Array.isArray(d.applications)){let c={},y=0;for(let u of d.applications){let m=u.jobId||"unknown";c[m]||(c[m]={applications:[]});let C=(u.questions||[]).map(b=>{let $={type:b.type||"input",question:b.question||"",answer:b.answer||""};return b.options&&(b.options.length<=tr?$.options=b.options:($.optionsOmitted=!0,y++)),b.page&&($.page=b.page),b.wasPreFilled&&($.wasPreFilled=!0),b.wasRetry&&($.wasRetry=!0),$});c[m].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})}Qe(r,c),console.log(` Phase 2: Rekeyed ${d.applications.length} applications by jobId (${Object.keys(c).length} unique jobs)`),y>0&&console.log(` Phase 2: Stripped bloated options from ${y} questions`)}else d&&!d.applications?console.log(" Phase 2: application-questions.json already in keyed format"):console.log(" Phase 2: No application-questions.json found");if(l.length>0){for(let c of l)delete i[c];Qe(o,i),console.log(` Phase 3: Removed ${l.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 Ze={};pe(Ze,{NAME:()=>rr,VERSION:()=>or,up:()=>lr});import*as F from"fs";import*as kn from"path";var or=2,rr="reset-unread-flags";function sr(e){return F.existsSync(e)?JSON.parse(F.readFileSync(e,"utf-8")):null}function ir(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 lr(e){let t=kn.join(e,"conversations.json"),n=sr(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}ir(t,n),console.log(` Reset unread=false on ${o} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var et={};pe(et,{NAME:()=>cr,VERSION:()=>ar,up:()=>pr});import*as M from"fs";import*as Cn from"path";var ar=3,cr="recompute-last-message-by",ur=new Set(["draft","confirmed","rejected","needs_review"]);function dr(e){return M.existsSync(e)?JSON.parse(M.readFileSync(e,"utf-8")):null}function fr(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 mr(e,t){if(!e||e.length===0)return"them";let n=e.filter(s=>!ur.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 pr(e){let t=Cn.join(e,"conversations.json"),n=dr(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 a of Object.keys(n)){let l=n[a];if(!l||typeof l!="object")continue;Object.prototype.hasOwnProperty.call(l,"_cardSynced")&&(delete l._cardSynced,s++);let d=Array.isArray(l.messages)?l.messages:[],c=mr(d,o),y=c==="me"?"awaiting_reply":"unanswered_by_us";l.lastMessageBy!==c&&i++,l.lastMessageBy=c,l.conversationStatus=y,r++}if(r===0){console.log(" No conversations to recompute.");return}fr(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 tt={};pe(tt,{NAME:()=>gr,VERSION:()=>hr,up:()=>br});import*as q from"fs";import*as xn from"path";var hr=4,gr="split-connection-timestamps",yr=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function wr(e){if(!q.existsSync(e))return null;let t=q.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function Sr(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";try{q.writeFileSync(o,n)}catch(r){try{q.unlinkSync(o)}catch{}throw r}try{q.renameSync(o,e)}catch(r){try{q.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 br(e){let t=xn.join(e,"people.json"),n=wr(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++,yr.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}Sr(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".')}await $e();Re();var kr=vr(import.meta.url),Cr=k.dirname(kr),xr=k.dirname(k.dirname(Cr));function Ar(e){return e&&(e==="~"?ot.homedir():e.startsWith("~/")||e.startsWith("~\\")?k.join(ot.homedir(),e.slice(2)):e)}var U=process.env.DATA_DIR?k.resolve(Ar(process.env.DATA_DIR)):xr,re=k.join(U,"collected-profiles"),se=k.join(U,"market-research"),T=k.join(U,".schema-version"),An=[k.join(re,".schema-version"),k.join(se,".schema-version")],z=4,J=[{version:1,module:Xe},{version:2,module:Ze},{version:3,module:et},{version:4,module:tt}];(function(){J.sort((o,r)=>o.version-r.version);let t=new Set;for(let o of J){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=J.length?J[J.length-1].version:0;if(n!==z)throw new Error(`MIGRATION_REGISTRY: highest version v${n} but CURRENT_SCHEMA_VERSION=${z}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let o=0;o<J.length;o++){let r=o+1;if(J[o].version!==r)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${r} at position ${o} but found v${J[o].version}`)}})();function In(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
|
|
248
248
|
[FATAL] DATA_DIR is set to whitespace ("${process.env.DATA_DIR}"). Either unset it or set a real path.
|
|
249
|
-
`),process.exit(1));try{f.mkdirSync(
|
|
250
|
-
[FATAL] Cannot create DATA_DIR (${
|
|
251
|
-
`),process.exit(1))}let e;try{e=f.statSync(
|
|
252
|
-
[FATAL] DATA_DIR (${
|
|
249
|
+
`),process.exit(1));try{f.mkdirSync(U,{recursive:!0})}catch(t){t.code!=="EEXIST"&&(console.error(`
|
|
250
|
+
[FATAL] Cannot create DATA_DIR (${U}): ${t.message}`),console.error(` Fix the path or permissions and try again.
|
|
251
|
+
`),process.exit(1))}let e;try{e=f.statSync(U)}catch(t){console.error(`
|
|
252
|
+
[FATAL] DATA_DIR (${U}) is not accessible: ${t.message}
|
|
253
253
|
`),process.exit(1)}e.isDirectory()||(console.error(`
|
|
254
|
-
[FATAL] DATA_DIR (${
|
|
255
|
-
`),process.exit(1));for(let t of[
|
|
254
|
+
[FATAL] DATA_DIR (${U}) exists but is NOT a directory.`),console.error(` Looks like a typo \u2014 your DATA_DIR points at a file. Fix the path and try again.
|
|
255
|
+
`),process.exit(1));for(let t of[re,se]){let n;try{n=f.lstatSync(t)}catch(o){if(o.code==="ENOENT")continue;throw o}n.isSymbolicLink()&&(console.error(`
|
|
256
256
|
[FATAL] Data subdirectory is a symbolic link: ${t}`),console.error(" Refusing to follow \u2014 backup code would copy whatever the link points at."),console.error(` If this is intentional, replace the symlink with the real directory.
|
|
257
|
-
`),process.exit(1))}}var
|
|
258
|
-
[FATAL] Another plugin process is currently running migrations:`),console.error(` PID: ${t.pid}`),console.error(` Started at: ${t.startedAt||"(unknown)"}`),console.error(` Lock file: ${
|
|
257
|
+
`),process.exit(1))}}var oe=k.join(U,".migration-lock");function Er(){let e=JSON.stringify({pid:process.pid,startedAt:new Date().toISOString()},null,2);try{return f.writeFileSync(oe,e,{flag:"wx"}),!0}catch(o){if(o.code!=="EEXIST")throw o}let t;try{t=JSON.parse(f.readFileSync(oe,"utf-8"))}catch{t=null}if(t&&Number.isInteger(t.pid)){let o=!1;try{process.kill(t.pid,0),o=!0}catch(i){i.code==="EPERM"&&(o=!0)}let r=24*60*60*1e3,s=1/0;if(t.startedAt){let i=Date.parse(t.startedAt);Number.isNaN(i)||(s=Date.now()-i)}o&&s<r&&(console.error(`
|
|
258
|
+
[FATAL] Another plugin process is currently running migrations:`),console.error(` PID: ${t.pid}`),console.error(` Started at: ${t.startedAt||"(unknown)"}`),console.error(` Lock file: ${oe}
|
|
259
259
|
`),console.error(" Wait for it to finish, or if you're sure it's not actually running,"),console.error(` delete the lock file manually and re-run.
|
|
260
|
-
`),process.exit(1)),o&&s>=r&&console.log(` Lock holder PID ${t.pid} is alive but lock is older than 24h \u2014 assuming PID reuse, taking over.`)}console.log(` Stale migration lock from PID ${t?.pid??"?"} found \u2014 taking over.`);let n=
|
|
260
|
+
`),process.exit(1)),o&&s>=r&&console.log(` Lock holder PID ${t.pid} is alive but lock is older than 24h \u2014 assuming PID reuse, taking over.`)}console.log(` Stale migration lock from PID ${t?.pid??"?"} found \u2014 taking over.`);let n=oe+".tmp";try{return f.writeFileSync(n,e),f.renameSync(n,oe),!0}catch(o){try{f.unlinkSync(n)}catch{}throw new Error(`Failed to take over stale migration lock: ${o.message}`)}}function rt(){try{if(JSON.parse(f.readFileSync(oe,"utf-8")).pid!==process.pid)return;f.unlinkSync(oe)}catch{}}var En=!1;function _r(){if(En)return;En=!0;let e=()=>{try{rt()}catch{}process.exit(1)};for(let t of["SIGINT","SIGTERM","SIGHUP"])try{process.on(t,e)}catch{}}function $r(){if(f.existsSync(T))return;let e=[],t=[];for(let s of An)if(f.existsSync(s))try{e.push({file:s,schema:Te(s)})}catch(i){t.push({file:s,err:i.message})}if(t.length>0){console.error(`
|
|
261
261
|
[FATAL] Legacy .schema-version file(s) corrupted \u2014 cannot determine current schema version:`);for(let s of t)console.error(` ${s.file}: ${s.err}`);console.error(`
|
|
262
262
|
Repair the file(s) (likely just an integer 'version' field \u2014 see another working DATA_DIR`),console.error(` or default to {"version":1,"migrations":[]} if you're sure you're on schema v1) and try again.
|
|
263
263
|
`),process.exit(1)}if(e.length===0)return;let n=e.map(s=>s.schema.version);if(new Set(n).size>1){console.error(`
|
|
264
264
|
[FATAL] Legacy .schema-version files disagree:`);for(let s of e)console.error(` ${s.file}: v${s.schema.version}`);console.error(" This means a previous migration crashed between writing the two files."),console.error(` Reconcile manually (delete the wrong one, keep the right one), then re-run.
|
|
265
|
-
`),process.exit(1)}let o=e[0].schema,r=
|
|
266
|
-
[FATAL] ${
|
|
267
|
-
`),process.exit(1)}let t=
|
|
268
|
-
[FATAL] ${
|
|
269
|
-
`),process.exit(1)),t}function
|
|
270
|
-
[FATAL] Your data schema is at v${t}, but this plugin build only understands up to v${
|
|
271
|
-
`),console.error(" Options:"),console.error(" A) Re-install the newer plugin version that produced this schema, then"),console.error(" (if you really want to downgrade) run 'rollback-data --confirm' first,"),console.error(" then re-install the older plugin version."),console.error(` B) Manually restore your data from the *.pre-vNNN.backup files matching v${
|
|
265
|
+
`),process.exit(1)}let o=e[0].schema,r=T+".tmp";try{f.writeFileSync(r,JSON.stringify(o,null,2)),f.renameSync(r,T)}catch(s){try{f.unlinkSync(r)}catch{}throw s}for(let s of An)if(f.existsSync(s))try{f.unlinkSync(s)}catch{}console.log(` Consolidated legacy .schema-version files \u2192 ${T}`)}function Ir(e){if(e===null||typeof e!="object"||Array.isArray(e))return null;let t=Number(e.version);return!Number.isFinite(t)||t<0||!Number.isInteger(t)?null:(e.version=t,Array.isArray(e.migrations)||(e.migrations=[]),e)}function Te(e){let t=f.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function _n(){if($r(),!f.existsSync(T))return{version:0,migrations:[]};let e;try{e=Te(T)}catch(n){if(n.code==="ENOENT")return{version:0,migrations:[]};console.error(`
|
|
266
|
+
[FATAL] ${T} is not valid JSON: ${n.message}`),console.error(" Refusing to proceed \u2014 silently treating it as 'fresh install' would re-apply"),console.error(" every migration on top of already-migrated data and corrupt it."),console.error(' Repair the file (set it to {"version":N,"migrations":[...]} matching your real state)'),console.error(` or restore from the *.pre-vNNN.backup files if you have them.
|
|
267
|
+
`),process.exit(1)}let t=Ir(e);return t===null&&(console.error(`
|
|
268
|
+
[FATAL] ${T} has invalid structure:`),console.error(` ${JSON.stringify(e)?.slice(0,200)||"(unparseable)"}`),console.error(' Expected shape: { "version": <integer>, "migrations": [...] }'),console.error(` Repair the file or restore from a backup.
|
|
269
|
+
`),process.exit(1)),t}function Rn(e){try{f.chmodSync(e,384)}catch{}}function Rr(e){if(In(),f.existsSync(T))try{if(f.lstatSync(T).isSymbolicLink())throw new Error(`SCHEMA_FILE is a symbolic link: ${T}. Refusing to write through it (potential symlink attack). Remove the link and re-run.`)}catch(n){if(n.message?.includes("symbolic link"))throw n}let t=T+".tmp";try{f.writeFileSync(t,JSON.stringify(e,null,2)),Rn(t),f.renameSync(t,T)}catch(n){try{f.unlinkSync(t)}catch{}throw n}}function $n(e,t){if(!f.existsSync(e))return;let n=`.pre-v${String(t).padStart(3,"0")}.backup`,o=f.readdirSync(e).filter(a=>{let l=a.toLowerCase();if(l.includes(".backup")||l===".schema-version"||l.endsWith(".tmp"))return!1;try{return f.statSync(k.join(e,a)).isFile()}catch{return!1}}),r=0,s=0,i=[];for(let a of o){let l=k.join(e,a),d=k.join(e,a+n);if(f.existsSync(d)){s++;continue}try{Tn(l,d),r++}catch(c){i.push({file:a,err:c.message})}}if(i.length>0){let a=new Error(`Failed to back up ${i.length} file(s) in ${k.basename(e)}/: `+i.map(l=>`${l.file} (${l.err})`).join(", "));throw a.code="BACKUP_INCOMPLETE",a}if(r>0||s>0){let a=s>0?` (${s} pre-existing backup(s) preserved)`:"";console.log(` Backed up ${r} files in ${k.basename(e)}/ (${n})${a}`)}}var nt=!1;async function Dn(){if(nt)return;In(),Nr();let t=_n().version;if(t===z){nt=!0;return}_r(),Er(),process.on("exit",rt),t>z&&(console.error(`
|
|
270
|
+
[FATAL] Your data schema is at v${t}, but this plugin build only understands up to v${z}.`),console.error(" This usually means you downgraded the plugin without rolling back data."),console.error(` Operating on the newer-format data with this build would risk corrupting it.
|
|
271
|
+
`),console.error(" Options:"),console.error(" A) Re-install the newer plugin version that produced this schema, then"),console.error(" (if you really want to downgrade) run 'rollback-data --confirm' first,"),console.error(" then re-install the older plugin version."),console.error(` B) Manually restore your data from the *.pre-vNNN.backup files matching v${z}.
|
|
272
272
|
`),process.exit(1)),console.log(`
|
|
273
|
-
\u{1F4E6} Schema migration: v${t} \u2192 v${
|
|
273
|
+
\u{1F4E6} Schema migration: v${t} \u2192 v${z}`);let n=Lr();if(!n.ok){console.error(`
|
|
274
274
|
[FATAL] Cannot migrate \u2014 current data is corrupted:`);for(let r of n.broken)console.error(` ${r.dir}/${r.file}: ${r.err}`);console.error(`
|
|
275
275
|
Migration ABORTED. No backups taken, no files modified.`),console.error(" Repair the file(s) above (restore from your own backup, or fix manually)"),console.error(` and try again. To inspect: 'ilml linkedin migrate-status'.
|
|
276
|
-
`),process.exit(1)}let o=
|
|
277
|
-
Running migration v${String(r.version).padStart(3,"0")}...`);let s=`v${String(r.version).padStart(3,"0")}`,i=`.pre-${s}.backup`;try{
|
|
276
|
+
`),process.exit(1)}let o=J.filter(r=>r.version>t);for(let r of o){console.log(`
|
|
277
|
+
Running migration v${String(r.version).padStart(3,"0")}...`);let s=`v${String(r.version).padStart(3,"0")}`,i=`.pre-${s}.backup`;try{$n(re,r.version),$n(se,r.version)}catch(l){console.error(`
|
|
278
278
|
[FATAL] Pre-migration backup failed for ${s}: ${l.message}`),console.error(" Migration NOT applied. Repair the underlying issue (disk space, permissions, locked files)"),console.error(` and re-run the plugin command.
|
|
279
|
-
`),process.exit(1)}let
|
|
280
|
-
[FATAL] Migration ${s} failed: ${l.message}`),console.error(` Attempting auto-rollback from ${i} files...`);try{let
|
|
281
|
-
`)):(console.error(` [!] Auto-rollback INCOMPLETE: ${
|
|
282
|
-
`))}catch(
|
|
283
|
-
`)}process.exit(1)}try{let l=new Date().toISOString(),
|
|
279
|
+
`),process.exit(1)}let a=r.module;try{await a.up(re,se)}catch(l){console.error(`
|
|
280
|
+
[FATAL] Migration ${s} failed: ${l.message}`),console.error(` Attempting auto-rollback from ${i} files...`);try{let d=Pr(i);d.ok?(console.error(` \u2713 Auto-rollback restored ${d.restored} files. Schema stays at v${t}.`),console.error(` The migration will be retried on next plugin command. Fix the root cause first.
|
|
281
|
+
`)):(console.error(` [!] Auto-rollback INCOMPLETE: ${d.error}`),console.error(` Manual recovery: copy *${i} files back over the originals.
|
|
282
|
+
`))}catch(d){console.error(` [!] Auto-rollback CRASHED while restoring: ${d.message}`),console.error(` Manual recovery: copy *${i} files back over the originals.
|
|
283
|
+
`)}process.exit(1)}try{let l=new Date().toISOString(),d={version:r.version,name:a.NAME||s,appliedAt:l},c=_n();c.version=r.version,c.migratedAt=l,c.migrations=c.migrations||[],c.migrations.push(d),Rr(c)}catch(l){console.error(`
|
|
284
284
|
[FATAL] Migration ${s} applied successfully but schema version file could NOT be updated: ${l.message}`),console.error(` Your data is in the v${r.version} state but .schema-version still shows v${t}.`),console.error(` On next plugin command the runner will try to re-apply ${s}. If that migration is`),console.error(` idempotent (most are), it will succeed harmlessly. If you're unsure, run 'data-cleanup' and contact support.
|
|
285
|
-
`),process.exit(1)}console.log(` \u2713 Migration ${s} complete`)}try{
|
|
286
|
-
\u2713 Schema is now at v${
|
|
287
|
-
`)}var
|
|
288
|
-
\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=
|
|
289
|
-
`)}async function
|
|
285
|
+
`),process.exit(1)}console.log(` \u2713 Migration ${s} complete`)}try{zt()}catch{}try{cn()}catch{}nt=!0,rt(),console.log(`
|
|
286
|
+
\u2713 Schema is now at v${z}
|
|
287
|
+
`)}var Dr=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function Tr(e){let t=e.toLowerCase();return Dr.some(n=>n.test(t))}var Or=6e4;function jr(){let e=Date.now(),t=[];for(let n of[U,re,se]){if(!f.existsSync(n))continue;let o;try{o=f.readdirSync(n)}catch{continue}for(let r of o){if(!Tr(r))continue;let s=k.join(n,r);try{let i=f.statSync(s);if(!i.isFile()||e-i.mtimeMs<Or)continue;t.push({dir:n,file:r,fullPath:s})}catch{}}}return t}function Nr({verbose:e=!1}={}){let t=jr();if(t.length===0)return e&&console.log(" No orphan .tmp files found."),0;let n=0;for(let o of t)try{f.unlinkSync(o.fullPath),n++,e&&console.log(` Removed orphan: ${k.basename(o.dir)}/${o.file}`)}catch(r){console.warn(` [!] Could not remove orphan ${o.fullPath}: ${r.message}`)}return!e&&n>0&&console.log(` Cleaned up ${n} orphan .tmp file(s) from previous run.`),n}function Lr(){let e=[];for(let t of[re,se]){if(!f.existsSync(t))continue;let n=f.readdirSync(t).filter(o=>{let r=o.toLowerCase();return r.endsWith(".json")&&!r.includes(".backup")});for(let o of n){let r=k.join(t,o);try{Te(r)}catch(s){e.push({dir:k.basename(t),file:o,err:s.message})}}}return e.length===0?{ok:!0}:{ok:!1,broken:e}}function Pr(e){let t=[],n=[];for(let s of[re,se]){if(!f.existsSync(s))continue;let i=f.readdirSync(s).filter(a=>a.endsWith(e));for(let a of i){let l=k.join(s,a),d=a.slice(0,-e.length),c=k.join(s,d);try{d.toLowerCase().endsWith(".json")?Te(l):f.accessSync(l,f.constants.R_OK),t.push({backupPath:l,originalPath:c,fileName:a,dir:s})}catch(y){n.push(`${k.basename(s)}/${a}: ${y.message}`)}}}if(n.length>0)return{ok:!1,error:n.join("; "),restored:0};let o=0,r=[];for(let{backupPath:s,originalPath:i,fileName:a,dir:l}of t)try{Tn(s,i),o++}catch(d){r.push(`${k.basename(l)}/${a}: ${d.message}`)}return r.length>0?{ok:!1,error:r.join("; "),restored:o}:{ok:!0,restored:o}}function Tn(e,t){let n=t+".tmp";try{f.copyFileSync(e,n),Rn(n)}catch(o){try{f.unlinkSync(n)}catch{}throw o}try{f.renameSync(n,t)}catch(o){try{f.unlinkSync(n)}catch{}if(o.code==="EPERM"||o.code==="EBUSY"){let r=new Error(`File locked: ${t} (${o.code}). On Windows this is usually antivirus / Search Indexer / the file open in another app. Close anything that might be holding it and re-run.`);throw r.code=o.code,r}throw o}}await Dn();var Ce=process.argv.slice(2),Oe=Ce.find(e=>e.startsWith("--source="))?.split("=")[1]||"scouted",st=parseInt(Ce.find(e=>e.startsWith("--max="))?.split("=")[1]||"50"),ie=parseInt(Ce.find(e=>e.startsWith("--revisit="))?.split("=")[1]||"0"),Fr=Ce.includes("--list"),Mr=Ce.includes("--verbose");function qr(){let e=fn();console.log(`
|
|
288
|
+
\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=Je();if(console.log(` Unconnected recruiters: ${t.length}`),ie>0){let n=We(ie);console.log(` Revisit (>${ie}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
|
|
289
|
+
`)}async function Ur(){if(console.log(`
|
|
290
290
|
LinkedIn Profile Visitor
|
|
291
|
-
`),
|
|
291
|
+
`),Fr){qr();return}let e=[],t="";if(ie>0?(e=We(ie),t=`revisit (>${ie} days)`):Oe==="people"?(e=Je(),t="unconnected recruiters (people.json)"):Oe==="scouted"||Oe==="discovered"?(e=hn(),t="discovered recruiters (market-research)"):(e=pn({limit:st}),t="smart (new \u2192 active \u2192 unconnected \u2192 revisit)"),console.log(` Source: ${t}`),console.log(` Candidates: ${e.length}`),console.log(` Max visits: ${st}
|
|
292
292
|
`),e.length===0){console.log(` No profiles to visit. Run npm run scout first.
|
|
293
|
-
`);return}let n=
|
|
293
|
+
`);return}let n=vn("visit",Oe),{browser:o,page:r}=await Jt();try{let s=await mn(r,e,{maxVisits:st,verbose:Mr,skipRecentDays:ie>0?0:7});console.log(`
|
|
294
294
|
\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
|
|
295
|
-
`),
|
|
296
|
-
Error:`,s.message),
|
|
295
|
+
`),Ke(n,"success",s)}catch(s){console.error(`
|
|
296
|
+
Error:`,s.message),Ke(n,"error",null,s.message)}finally{await o.close().catch(()=>{})}}Ur().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
|
|
2
|
-
\u2550\u2550 Warm-scan: ${
|
|
3
|
-
`);var
|
|
1
|
+
import"dotenv/config";import*as A from"fs";import*as u from"path";import{fileURLToPath as q}from"url";import{getPluginConfig as J}from"@ilivemylife/graph-sdk";var N={day:"r86400","24h":"r86400",today:"r86400",week:"r604800","7d":"r604800",month:"r2592000","30d":"r2592000"},E=e=>/^(1|true|yes|on)$/i.test(String(e??"").trim());function L(e){return String(e??"").split(/[,\s|]+/).map(n=>n.trim()).filter(Boolean)}function O(e=process.env){let n=(e.JOB_SEARCH_KEYWORDS||"").trim();if(!n)return null;let t=new URLSearchParams;t.set("keywords",n);let r=(e.JOB_SEARCH_LOCATION||"").trim();r&&t.set("location",r);let o=(e.JOB_SEARCH_GEO_ID||"").trim();o&&t.set("geoId",o);let s=parseInt(e.JOB_SEARCH_RADIUS_MILES,10);Number.isFinite(s)&&s>0&&t.set("distance",String(s)),E(e.JOB_SEARCH_EASY_APPLY)&&t.set("f_AL","true"),E(e.JOB_SEARCH_IN_NETWORK)&&t.set("f_JIYN","true");let l=L(e.JOB_SEARCH_COMPANY_IDS);l.length&&t.set("f_C",l.join(","));let a=(e.JOB_SEARCH_DATE_POSTED||"").trim().toLowerCase();a&&t.set("f_TPR",N[a]||a),t.set("origin","JOB_SEARCH_PAGE_JOB_FILTER");let j="https://www.linkedin.com/jobs/search/?"+t.toString(),w=(e.JOB_SEARCH_EXTRA_PARAMS||"").trim().replace(/^[?&]+/,"");return w&&(j+="&"+w),j}var P=await J();for(let[e,n]of Object.entries(P))process.env[e]===void 0&&(process.env[e]=n);var D=O(process.env);D&&(process.env.LINKEDIN_SEARCH_URL=D);var te=process.env.LINKEDIN_NAME,f=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),oe=Object.freeze(new Set([f.DRAFT,f.CONFIRMED,f.NEEDS_REVIEW])),se=Object.freeze(new Set([f.DRAFT,f.CONFIRMED,f.REJECTED,f.NEEDS_REVIEW]));import"dotenv/config";import*as c from"fs";import*as i from"path";import{fileURLToPath as M}from"url";var B=M(import.meta.url),U=i.dirname(i.dirname(B)),d=process.env.DATA_DIR?i.resolve(process.env.DATA_DIR,"collected-profiles"):i.join(U,"collected-profiles"),ae=i.join(d,"people.json"),ue=i.join(d,"jobs.json"),pe=i.join(d,"quota.json"),fe=i.join(d,"conversations.json"),de=i.join(d,"sync-state.json"),me=i.join(d,"application-questions.json"),ge=i.join(d,"profile-history"),I=(e,n)=>{let t=parseInt(e,10);return Number.isInteger(t)&&t>=0?t:n},Se=I(process.env.CONNECT_MAX_DAILY,20),he=I(process.env.CONNECT_MAX_WEEKLY,100);function C(e,n){if(!c.existsSync(e))return null;try{let t=JSON.parse(c.readFileSync(e,"utf-8"));try{c.copyFileSync(e,e+".backup")}catch{}return t}catch(t){console.error(`[!] ${n} is corrupted: ${t.message}`),console.error(` File: ${i.resolve(e)}`);let r=e+".backup";if(c.existsSync(r))try{let o=JSON.parse(c.readFileSync(r,"utf-8"));console.error(` Restored from ${r}`);try{c.copyFileSync(r,e)}catch{}return o}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{c.copyFileSync(e,e+".corrupted")}catch{}return null}}var H=u.dirname(q(import.meta.url)),$=process.env.DATA_DIR?u.resolve(process.env.DATA_DIR,"collected-profiles"):u.join(H,"collected-profiles"),g={};for(let e of process.argv.slice(2)){let n=e.match(/^--([^=]+)(?:=(.*))?$/);n&&(g[n[1]]=n[2]??!0)}var m=g.group||null,b=typeof g["min-seniority"]=="string"?g["min-seniority"]:null,R=!!g["show-all"],p={},S=process.env.DATA_DIR&&u.resolve(process.env.DATA_DIR,"target-companies.json");if(S&&A.existsSync(S))try{p=JSON.parse(A.readFileSync(S,"utf8"))}catch(e){console.error(`Ignoring malformed ${S}: ${e.message}`)}(!p||typeof p!="object"||Array.isArray(p))&&(p={});var T=typeof g.companies=="string"?g.companies.split(","):m?p[m]?.companies:null,y=Array.isArray(T)?T.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(p);m&&!p[m]&&console.error(` (group "${m}" not found)`),e.length?console.error(` --group=<name> your groups: ${e.join(", ")}`):console.error(` or define named groups in ${S||"<DATA_DIR>/target-companies.json"}`),process.exit(1)}var k=C(u.join($,"people.json"),"people.json")||{},v=C(u.join($,"conversations.json"),"conversations.json")||{};Object.keys(k).length||(console.error("No contacts found yet \u2014 run `ilml linkedin sync` first to build your network, then warm-scan."),process.exit(1));var _=new Map;for(let[e,n]of Object.entries(v))typeof n.isConnection=="boolean"&&_.set(e,{isConnection:n.isConnection,conv:n});function V(e){if(e.name&&_.has(e.name)){let n=_.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 x={intern:0,junior:1,mid:2,senior:3,lead:4,staff:4,principal:5,director:5,vp:6};function W(e,n){if(!n)return!0;if(!e)return!1;let t=e.toLowerCase(),r=x[n.toLowerCase()];if(r==null)return!0;for(let[o,s]of Object.entries(x))if(t.includes(o)&&s>=r)return!0;return!1}function Y(e,n){return[e.company,e.title,n?.personContext?.title,n?.personContext?.company].filter(Boolean).join(" | ").toLowerCase()}function Q(e){let n=e.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${n}\\b`,"i")}var K=y.map(e=>({name:e,re:Q(e)})),h={};for(let e of y)h[e]={connected:[],notConnected:[],unknown:[]};for(let[e,n]of Object.entries(k)){let t=n.name?v[n.name]:null,r=Y(n,t);if(r)for(let{name:o,re:s}of K){if(!s.test(r))continue;if(!W(n.title||t?.personContext?.title,b))break;let l=V(n),a={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?h[o].connected.push(a):l.is===!1?h[o].notConnected.push(a):h[o].unknown.push(a);break}}var G=b?` (min-seniority=${b})`:"",z=m?`group "${m}"`:`${y.length} target companies`;console.log(`
|
|
2
|
+
\u2550\u2550 Warm-scan: ${z}${G} \u2550\u2550
|
|
3
|
+
`);var X=Object.entries(h).sort((e,n)=>n[1].connected.length-e[1].connected.length),F=0;for(let[e,n]of X){let t=n.connected.length+n.notConnected.length+n.unknown.length;F+=n.connected.length;let r=`${e.padEnd(18)} connected: ${n.connected.length}, other: ${n.notConnected.length+n.unknown.length}, total seen: ${t}`;if(console.log(r),n.connected.length>0)for(let s of n.connected){let l=(s.title||"").slice(0,55),a=s.convStatus?` [${s.convStatus}]`:"",j=s.lastMsg?` (${s.lastMsg.slice(0,10)})`:"";console.log(` \xB7 ${s.name.padEnd(28)} ${l}${a}${j}`)}let o=[...n.notConnected,...n.unknown];if(o.length>0&&(R||o.length<=5)){console.log(" seen but not 1st-degree:");for(let s of o.slice(0,R?o.length:5))console.log(` ${s.name.padEnd(28)} ${(s.title||"").slice(0,55)}`)}else o.length>5&&console.log(` seen but not 1st-degree: ${o.length} \u2014 add --show-all or --companies="${e}" to expand`);console.log("")}console.log(`Total 1st-degree at target companies: ${F}`);
|
package/ilml-plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkedin",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.13",
|
|
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." },
|
|
@@ -294,7 +294,16 @@
|
|
|
294
294
|
"fields": [
|
|
295
295
|
{ "key": "City", "label": "Default city", "type": "text", "required": false, "default": "Toronto" },
|
|
296
296
|
{ "key": "MAX_APPLY_FOR_RUN", "label": "Max applications per run", "type": "number", "required": false, "default": 50 },
|
|
297
|
-
{ "key": "LINKEDIN_SEARCH_URL", "label": "Default job search URL", "type": "url", "required": false, "hint": "LinkedIn jobs search URL
|
|
297
|
+
{ "key": "LINKEDIN_SEARCH_URL", "label": "Default job search URL", "type": "url", "required": false, "hint": "Full LinkedIn jobs search URL. Used only when the JOB_SEARCH_* fields below are empty — otherwise the plugin builds the URL from those." },
|
|
298
|
+
{ "key": "JOB_SEARCH_KEYWORDS", "label": "Search keywords / titles", "type": "text", "required": false, "hint": "e.g. Software Engineer OR Senior Software Engineer OR Engineering Manager. Setting this makes the plugin BUILD the search URL from the JOB_SEARCH_* fields below (overrides the URL above)." },
|
|
299
|
+
{ "key": "JOB_SEARCH_LOCATION", "label": "Location (city)", "type": "text", "required": false, "hint": "e.g. Vancouver, British Columbia, Canada" },
|
|
300
|
+
{ "key": "JOB_SEARCH_GEO_ID", "label": "Location geoId (precise)", "type": "text", "required": false, "hint": "LinkedIn geoId for the city — more reliable than text, and required for the radius. e.g. Vancouver = 103366113" },
|
|
301
|
+
{ "key": "JOB_SEARCH_RADIUS_MILES", "label": "Radius around city (miles)", "type": "number", "required": false, "hint": "e.g. 50 (25 is often too tight — a metro area's nearby cities are commuting distance). Needs a geoId to take effect." },
|
|
302
|
+
{ "key": "JOB_SEARCH_EASY_APPLY", "label": "Easy Apply only", "type": "text", "required": false, "hint": "true / false. The apply bot can only submit Easy Apply jobs, so set true for apply." },
|
|
303
|
+
{ "key": "JOB_SEARCH_DATE_POSTED", "label": "Date posted", "type": "text", "required": false, "hint": "day, week, or month" },
|
|
304
|
+
{ "key": "JOB_SEARCH_IN_NETWORK", "label": "In your network only", "type": "text", "required": false, "hint": "true / false — only jobs at companies where you have connections." },
|
|
305
|
+
{ "key": "JOB_SEARCH_COMPANY_IDS", "label": "Company IDs (optional)", "type": "text", "required": false, "hint": "comma list of LinkedIn company IDs to restrict to." },
|
|
306
|
+
{ "key": "JOB_SEARCH_EXTRA_PARAMS", "label": "Extra filters (advanced, verbatim)", "type": "text", "required": false, "hint": "For filters LinkedIn encodes opaquely (workplace, experience, job type, salary): open a search with those filters set in LinkedIn, then copy the f_... parts from the address bar and paste them here verbatim, e.g. f_SAL=f_SA_id_225001:272001&f_JT=F,C — appended as-is, nothing guessed." }
|
|
298
307
|
]
|
|
299
308
|
},
|
|
300
309
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilml-plugin-linkedin",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.13",
|
|
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": [
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
"homepage": "https://github.com/iLiveMyLife/linkedInJobsAutoApply#readme",
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@ilivemylife/graph-sdk": "^1.0.
|
|
87
|
+
"@ilivemylife/graph-sdk": "^1.0.29",
|
|
88
88
|
"dotenv": "^16.4.5",
|
|
89
89
|
"puppeteer": "^22.15.0",
|
|
90
90
|
"puppeteer-extra": "^3.3.6",
|