ilml-plugin-linkedin 1.20.31 → 1.20.33
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 +8 -0
- package/dist/messageBot.mjs +1 -1
- package/dist/run.mjs +35 -35
- package/dist/syncAll.mjs +1 -1
- package/dist/version.mjs +1 -1
- package/ilml-plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.33
|
|
4
|
+
- **Fixed: replies couldn't be sent into some conversations because one of the other person's messages was being dropped when the thread was saved.** When someone sent two messages where the later one ended with the same words as an earlier one (for example "Tried calling you" and then "again tried calling you"), the save step mistook the second for a duplicate of the first and discarded it. The stored copy of the thread then held one fewer message than LinkedIn actually shows, so the safety check before sending — "did they reply since we last synced?" — saw the mismatch, assumed a new unread message had arrived, and aborted the send every time, making that conversation impossible to reply to automatically. The duplicate check now only merges two messages when they share the same timestamp (a true duplicate of a single message); distinct messages that merely end with the same words are kept. Genuine duplicates are still removed.
|
|
5
|
+
- No schema change. A thread that already lost a message repairs itself on the next sync.
|
|
6
|
+
|
|
7
|
+
## 1.20.32
|
|
8
|
+
- **Fixed: Easy Apply silently gave up on some jobs and never tried them again.** On a subset of postings, clicking Easy Apply registered but the application form never actually opened (seen on sponsored postings that fire ad-tracking on click). The applier didn't verify the form had opened — it assumed the click worked, found nothing to fill, treated the job as a dead end after a few empty retries, and then marked it as permanently handled, so it was never retried on any later run. Over the past month this quietly dropped a large share of otherwise-applicable jobs. The applier now confirms the form really opened, re-clicks a few times if it didn't, and if it still won't open it skips the job *softly* — leaving it in the pool to try again next run instead of discarding it for good. Jobs whose form opens normally are unaffected.
|
|
9
|
+
- No schema change.
|
|
10
|
+
|
|
3
11
|
## 1.20.31
|
|
4
12
|
- **Fixed: the "wrong recipient?" safety check blocked every message to a contact whose name is stored with a title stuck to it (e.g. "Dr.Mikhail Spokoyny"), even when the greeting was right.** The check compares the first word of the contact's stored name against the name you greet. When LinkedIn had saved the name with the title glued on ("Dr.Mikhail"), a correct "Hi Mikhail" didn't match "Dr.Mikhail" and the message was aborted on every send — and the same would hit anyone whose stored name starts with a title. The check now ignores a leading title (Dr./Mr./Mrs./Ms./Prof.), whether it's glued to the name or separated by a space, so correct greetings go through while genuine wrong-recipient mix-ups are still caught.
|
|
5
13
|
- **New: `messages --reject <name>` / `messages --reject-batch @names.json` — retire drafts that can never be delivered.** When the other person has removed the connection or deactivated their account, LinkedIn never opens a message composer, so a saved draft fails on every `push-drafts` run and keeps cluttering the send queue. This marks those drafts as rejected (recording when and why) so push-drafts skips them for good. It only touches draft messages — real received or sent messages are never changed — and prints exactly what it changed.
|
package/dist/messageBot.mjs
CHANGED
|
@@ -6,7 +6,7 @@ var go=Object.defineProperty;var ae=(e,t)=>{for(var o in t)go(e,o,{get:t[o],enum
|
|
|
6
6
|
`),console.error(` Or add ILML_TOKEN=... to .env
|
|
7
7
|
`),process.exit(1));var Rt=Oo({token:_t});function Mo(e){if(!e||e<1e3)return`${e||0}ms`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let o=Math.floor(t/60),n=t%60;return n?`${o}m ${n}s`:`${o}m`}var Po={apply:"Job apply","apply-queue":"Job apply (queue)",scout:"Job scout",connect:"Connect run",funnel:"Recruiter funnel",messages:"Messages",sync:"Connections sync","sync-all":"Inbox sync",visit:"Profile visits",viewers:"Profile viewers",daily:"Daily run"};function Fo(e){let t=String(e||"").toLowerCase();return/authwall|session expired|not logged in|re-?auth|logged out|sign ?in/.test(t)?"LinkedIn asked to sign in again. Run `ilml linkedin login`, then run this again.":/protocoltimeout|callfunctionon|target closed|session closed|detached|execution context|browser.*disconnected|connection closed/.test(t)?"The browser stopped responding partway through. This is usually temporary \u2014 just run it again.":/err_aborted|err_network|err_connection|err_name_not_resolved|net::/.test(t)?"A page failed to load (network hiccup). Try running it again in a moment.":/navigation timeout|timeout.*exceeded|waiting for selector|waiting for/.test(t)?"A page took too long to load. Try again in a moment.":/invite limit|weekly.*limit|too many|rate.?limit|quota/.test(t)?"You've reached a LinkedIn sending limit for now. Wait a while, then try again.":"Something went wrong partway through. It is safe to run the command again."}function Lo({scriptName:e,mode:t,status:o,summaryLines:n,error:s,stoppedReason:r,duration:i}){let c=o==="error"||o==="stopped"?o==="error"?"\u2717":"\u26A0":o==="quota-reached"?"\u26A0":"\u2713",l=o==="error"?"stopped early":o==="stopped"?`stopped \u2014 ${r||"did not finish"}`:o==="quota-reached"?"daily limit reached":"done",a=Po[e]||e,u=t&&t!=="default"?` \xB7 ${t}`:"",d=new Date().toISOString().slice(0,16).replace("T"," "),m=i?` \xB7 ${Mo(i)}`:"",g=[];g.push(`## ${c} ${a} \u2014 ${l}`),g.push(`_${d}${u}${m}_`),g.push("");for(let p of n)g.push(`- ${p}`);if(o==="error"){g.push(""),g.push(`**What happened:** ${Fo(s)}`);let p=String(s||"").replace(/\s+/g," ").trim();p&&g.push(`_Technical detail: ${p.slice(0,140)}_`)}return g.join(`
|
|
8
8
|
`)}async function Fe({scriptName:e,mode:t,status:o,summaryLines:n,error:s,stoppedReason:r,duration:i}){let c=process.env.NODE_RUN_REPORTS;if(!c||!Array.isArray(n)||n.length===0)return;let l=Lo({scriptName:e,mode:t,status:o,summaryLines:n,error:s,stoppedReason:r,duration:i});try{await Rt.addMessage(c,l)}catch(a){console.error(`[runReport] Failed to post end-of-session message: ${a.message}`)}}var Bo=Uo(import.meta.url),qo=Q.dirname(Q.dirname(Bo)),Le=process.env.DATA_DIR?Q.resolve(process.env.DATA_DIR,"collected-profiles"):Q.join(qo,"collected-profiles"),Ue=Q.join(Le,"run-log.json");function Jo(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";L.writeFileSync(n,o);for(let s=0;s<3;s++)try{L.renameSync(n,e);return}catch(r){if(s<2&&(r.code==="EPERM"||r.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw r}}function Dt(){try{if(!L.existsSync(Ue))return{runs:[]};let e=JSON.parse(L.readFileSync(Ue,"utf-8"));return!e.runs||!Array.isArray(e.runs)?{runs:[]}:e}catch{return{runs:[]}}}function It(e){L.existsSync(Le)||L.mkdirSync(Le,{recursive:!0});try{Jo(Ue,e)}catch(t){console.error(`[!] Failed to save run-log.json: ${t.message}`)}}function Wo(e,t="default"){let o=Dt();for(let i of o.runs)i.status==="running"&&(i.status="interrupted",i.result="interrupted",i.timestamp&&(i.duration=Date.now()-new Date(i.timestamp).getTime()));let n=new Date,s={timestamp:n.toISOString(),script:e,mode:t,status:"running",result:null,duration:null,stats:null,error:null};o.runs.push(s);let r=Date.now()-30*24*60*60*1e3;return o.runs=o.runs.filter(i=>new Date(i.timestamp).getTime()>r),It(o),{script:e,mode:t,startTime:n.getTime(),runIndex:o.runs.length-1}}function Ho(e,t,o=null,n=null){if(!e)return;let s=null;try{let c=Dt(),l=c.runs[e.runIndex];if(l&&l.status==="running"&&l.script===e.script)l.status=t,l.result=t,l.duration=Date.now()-e.startTime,l.stats=o,l.error=n,s=l.duration;else{let a=c.runs.find(u=>u.script===e.script&&u.status==="running"&&u.timestamp===new Date(e.startTime).toISOString());a&&(a.status=t,a.result=t,a.duration=Date.now()-e.startTime,a.stats=o,a.error=n,s=a.duration)}It(c)}catch(c){console.error(`[runLog] Failed to end run: ${c.message}`)}let r=o&&Array.isArray(o.summaryLines)?o.summaryLines:null,i=o&&o.__deferReport===!0;if(r&&r.length>0&&process.env.NODE_RUN_REPORTS&&!i){let c=t==="error"?"error":o&&o.dailyLimitHit?"quota-reached":"done";Fe({scriptName:e.script,mode:e.mode,status:c,summaryLines:r,error:n,duration:s??Date.now()-e.startTime}).catch(l=>console.error(`[runLog] postRunReport failed: ${l.message}`))}}async function Tt(e,t,o,n,s={}){let{rethrow:r=!0}=s,i=Wo(e,t),c={},l=null;try{await o(c)}catch(m){l=m}let a=[];try{let m=n?n(c):[];a=(Array.isArray(m)?m:[]).filter(g=>typeof g=="string"&&g.trim())}catch(m){console.error(`[runLog] summarize threw (report still sent): ${m.message}`)}if(a.length===0&&l&&(a=["(crashed before any progress was recorded)"]),l){let m=t&&t!=="default"?` (${t})`:"";console.error(`
|
|
9
|
-
\u2717 ${e}${m} failed: ${l.message||l}`)}let u=Date.now()-i.startTime,d=!l&&c.stoppedReason?String(c.stoppedReason):null;try{Ho(i,l?"error":d?"partial":"success",{...c,summaryLines:a,__deferReport:!0},l?l.message||String(l):null)}catch(m){console.error(`[runLog] endRun failed: ${m.message}`)}if(a.length>0&&process.env.NODE_RUN_REPORTS){let m=l?"error":d?"stopped":c.dailyLimitHit?"quota-reached":"done";try{await Fe({scriptName:i.script,mode:i.mode,status:m,summaryLines:a,error:l?l.message||String(l):null,stoppedReason:d,duration:u})}catch(g){console.error(`[runLog] report post failed: ${g.message}`)}}if(l&&r)throw l;return c}import Xo from"puppeteer";import{addExtra as Zo}from"puppeteer-extra";import en from"puppeteer-extra-plugin-stealth";import Be from"fs";import Vo from"path";var Yo={headless:!1,defaultViewport:null,protocolTimeout:3e5},pe=new Set,Ot=!1;async function be(){let e=[...pe].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});pe.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function Go(){if(Ot)return;Ot=!0;let e=t=>async()=>{await be(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await be(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await be(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await be(),process.exit(0))}),process.on("exit",()=>{for(let t of pe)try{let o=t.process&&t.process();o&&o.kill("SIGKILL")}catch{}})}function qe(e){return Vo.join(e,".ilml-browser-session.json")}function Pt(e,t={}){try{Be.writeFileSync(qe(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function zo(e,t){try{let o=t.wsEndpoint&&t.wsEndpoint();Pt(e,o?{wsEndpoint:o}:{})}catch{}}function Ko(e){let t=Ft(e);t&&t.ownerPid===process.pid&&de(e)}function de(e){try{Be.rmSync(qe(e),{force:!0})}catch{}}function Ft(e){try{return JSON.parse(Be.readFileSync(qe(e),"utf-8"))}catch{return null}}function jt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Nt(e,t,o){let n,s=new Promise(i=>{n=setTimeout(()=>i(o),t)}),r=Promise.resolve(e).then(i=>(clearTimeout(n),i),i=>{throw clearTimeout(n),i});return Promise.race([r,s])}async function Mt(e,t,{probeTimeoutMs:o=4e3}={}){let n=Ft(t);if(!n)return"free";if(!n.wsEndpoint)return n.ownerPid&&n.ownerPid!==process.pid&&jt(n.ownerPid)?"live":(de(t),"free");let s=Symbol("timeout"),r=e.connect({browserWSEndpoint:n.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Nt(r,o,s);if(i===s)return r.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return de(t),"free";if(jt(n.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Nt(i.close().then(()=>!0).catch(()=>!1),o,!1))return de(t),"free";try{i.disconnect()}catch{}return"live"}function Qo(e,t){e&&(Go(),pe.add(e),e.on("disconnected",()=>{pe.delete(e),t&&de(t)}))}async function Lt(e,t={}){let o=dt(),n={...Yo,...t,userDataDir:o},s=`Could not open the LinkedIn automation Chrome profile at ${o}. 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,o)==="live")throw new Error(s);Pt(o);let r;try{r=await e.launch(n)}catch{if(await Mt(e,o)==="live")throw new Error(s);ht(o);try{r=await e.launch(n)}catch(c){throw Ko(o),new Error(`Could not open the LinkedIn automation Chrome profile at ${o}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${c.message})`)}}return Qo(r,o),zo(o,r),{browser:r,profileDir:o,needsSeed:mt(o)}}async function Ut(e,t){if(!t||!t.needsSeed)return!1;if(!ut())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(...at()),gt(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Jt=Zo(Xo);Jt.use(en());async function Wt(){let{browser:e,profileDir:t,needsSeed:o}=await Lt(Jt),n=await e.newPage();return await n.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Ut(n,{profileDir:t,needsSeed:o}),console.log(" Browser launched (persistent profile)."),{browser:e,page:n}}async function We(e){await we(e,"https://www.linkedin.com/messaging/",{readySelector:".msg-conversations-container__conversations-list, .msg-conversation-listitem",label:"inbox"})||(console.warn(" Inbox container not found via known selectors, proceeding..."),await E(3e3)),console.log(" Inbox loaded.")}async function Ht(e){let t=await e.evaluate(()=>{let o=document.querySelectorAll(".msg-conversation-listitem"),n=[];for(let s of o){let i=s.querySelector(".msg-conversation-listitem__participant-names, .msg-conversation-card__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")||null;if(!i)continue;let l=s.querySelector(".msg-conversation-card__message-snippet")?.textContent?.trim()?.replace(/\s+/g," ")||"";if(/^\s*Sponsored\b/i.test(l)||/^\s*Promoted\b/i.test(l))continue;let u=s.querySelector(".msg-conversation-listitem__time-stamp, .msg-conversation-card__time-stamp")?.textContent?.trim()||"",d=s.querySelector(".msg-conversation-card__convo-item-container--unread")!==null||s.querySelector(".msg-conversation-card__unread-count")!==null,m,g;if(l.startsWith("You:"))m="me",g=l.slice(4).trim();else{let f=i.split(" ")[0];if(l.startsWith(i+":")||l.startsWith(f+":")){m="them";let h=l.indexOf(":");g=l.slice(h+1).trim()}else m="them",g=l}let p=s.getBoundingClientRect();n.push({name:i,lastMessagePreview:g,lastMessageTime:u,unread:d,lastMessageBy:m,x:p.x+p.width/2,y:p.y+p.height/2})}return n});for(let o of t)o.lastMessageTime=Oe(o.lastMessageTime)||o.lastMessageTime;return t}async function Vt(e){return e.evaluate(()=>{let t=[".msg-conversations-container__conversations-list",".msg-conversations-container",'[class*="msg-conversations-container"] ul'];for(let n of t){let s=document.querySelector(n);if(s&&s.scrollHeight>s.clientHeight)return s.scrollBy(0,600),{scrollTop:s.scrollTop,scrollHeight:s.scrollHeight}}let o=document.querySelector(".msg-conversation-listitem");if(o){let n=o.parentElement;for(;n;){if(n.scrollHeight>n.clientHeight+10)return n.scrollBy(0,600),{scrollTop:n.scrollTop,scrollHeight:n.scrollHeight};n=n.parentElement}}return null})}async function tn(e){return e.evaluate(()=>{let t=/^status\s+is\s+|^online$|^offline$|^away$|^busy$|^active\s/i,o=['.msg-overlay-conversation-bubble__header a[href*="/in/"] .truncate','.msg-overlay-conversation-bubble__header a[href*="/in/"]','.msg-overlay-bubble-header__title a[href*="/in/"]',".msg-overlay-bubble-header__title",".msg-overlay-conversation-bubble__header .truncate",".msg-thread__link-to-profile","h2.msg-entity-lockup__entity-title",".msg-s-message-list-container h2",".msg-thread h2",".msg-conversations-container__title-row h2",'.msg-thread a[href*="/in/"]'];for(let s of o){let r=document.querySelector(s);if(r){let i=r.textContent?.trim()?.replace(/\s+/g," ");if(i&&i.length>1&&i.length<100&&!t.test(i))return i}}let n=document.querySelectorAll(".msg-s-message-list-container h2, .msg-thread h2");for(let s of n){let r=s.textContent?.trim();if(r&&r.length>1&&r.length<100&&!/conversation/i.test(r)&&!t.test(r))return r}return null})}async function Bt(e,t){let o=await tn(e);if(!o)return{match:!1,headerName:null};let n=t.split(" ")[0].toLowerCase(),s=t.toLowerCase().split(/[\s,]+/).filter(c=>c.length>1),r=o.toLowerCase();return{match:r.includes(n)||s.some(c=>r.includes(c)),headerName:o}}async function Je(e,t){let o=t.split(" ")[0],n=8e3,s=500,r=0;for(;r<n;){let{match:l,headerName:a}=await Bt(e,t);if(l)return await E(500),{verified:!0,headerName:a};let u=await e.evaluate(()=>{let d=document.querySelectorAll(".msg-s-message-group__name"),m=Array.from(d).map(p=>p.textContent?.trim()).filter(Boolean),g=document.querySelectorAll(".msg-s-event-listitem").length;return{allSenders:m,msgCount:g}});if(u.msgCount>0&&u.allSenders.some(m=>m.toLowerCase().includes(o.toLowerCase())))return await E(300),{verified:!0,headerName:o};await E(s),r+=s}let{match:i,headerName:c}=await Bt(e,t);return i?{verified:!0,headerName:c}:{verified:!1,headerName:c}}async function on(e){let o=0,n=0;for(let s=0;s<50;s++){let r=await e.evaluate(()=>{let i=document.querySelector(".msg-thread .msg-s-message-list-container")||document.querySelector(".msg-s-message-list-container");if(!i){let a=document.querySelector(".msg-overlay-conversation-bubble");a&&(i=a.querySelector(".msg-s-message-list-container")||a.querySelector(".msg-s-message-list"))}if(!i)return{found:!1};let c=i.querySelectorAll("li.msg-s-message-list__event").length;return i.scrollTop<=1?{found:!0,atTop:!0,msgCount:c}:(i.scrollTop=0,{found:!0,atTop:!1,msgCount:c})});if(!r.found||r.atTop)break;if(await E(800),r.msgCount===o){if(n++,n>=3)break}else n=0;o=r.msgCount}}async function ce(e,{scrollUp:t=!0}={}){t&&await on(e);let o=await e.evaluate(()=>{let n=[],s="",r=document.querySelector(".msg-thread .msg-s-message-list-container")||document.querySelector(".msg-s-message-list-container");if(!r){let u=document.querySelector(".msg-overlay-conversation-bubble");u&&(r=u.querySelector(".msg-s-message-list-container")||u.querySelector(".msg-s-message-list"))}if(!r)return n;let i=r.querySelectorAll("li.msg-s-message-list__event");if(i.length===0){let u=document.querySelector(".msg-overlay-conversation-bubble, .msg-overlay-list-bubble");u&&(i=u.querySelectorAll("li.msg-s-message-list__event, .msg-s-event-listitem"))}function c(u){let d=u.getAttribute("data-event-urn");if(!d)return null;let m=d.match(/,2-([A-Za-z0-9+/=]+)\)/);if(!m)return null;try{let p=atob(m[1]).match(/^(\d{13})/);if(!p)return null;let f=Number(p[1]);if(f>14200704e5&&f<20512224e5)return f}catch{}return null}let l="";function a(u,d,m){let g=c(u),f=u.querySelector(".msg-s-message-group__timestamp, time")?.textContent?.trim()||"",h=u.querySelector(".msg-s-event-listitem__message-bubble");if(!h)return null;let S=h.textContent?.trim()?.replace(/Open Emoji Keyboard|Reply to this message|React with[^.]+/g,"")?.replace(/^(?:👏\s*👍\s*😊\s*)+/,"")?.replace(/\s+/g," ")?.trim()||"";if(!S)return null;let y=g?new Date(g).toISOString():m?`${m} ${f}`.trim():f;return{sender:d,text:S,time:y}}for(let u of i){let d=u.querySelector("time.msg-s-message-list__time-heading");d&&(l=d.textContent?.trim()||l);let m=u.querySelectorAll(".msg-s-message-group");if(m.length>0)for(let g of m){let p=g.querySelector(".msg-s-message-group__name");p&&(s=p.textContent?.trim()||s);let f=s||"Unknown",h=g.querySelectorAll(".msg-s-event-listitem");for(let S of h){let y=a(S,f,l);y&&n.push(y)}}else{let g=u.querySelectorAll(".msg-s-event-listitem");for(let p of g){let f=p.querySelector(".msg-s-message-group__name");f&&(s=f.textContent?.trim()||s);let S=a(p,s||"Unknown",l);S&&n.push(S)}}}if(n.length===0){let u=document.querySelectorAll(".msg-s-event-listitem--group-a11y-heading");for(let d of u){let m=d.textContent?.trim();m&&n.push({sender:"",text:m,time:""})}}return n});for(let n of o)(!n.time||!/^\d{4}-\d{2}-\d{2}T/.test(n.time))&&(n.time=Oe(n.time)||n.time);return o}function le(e){return String(e||"").replace(/\s+/g," ").trim().toLowerCase()}function $e(e,t){if(!e||e.length===0)return t||[];if(!t||t.length===0)return e;let o=m=>le(m.sender)+"||"+le(m.text).replace(/\s+/g,""),n=new Set(t.map(o)),s=new Set(e.map(o)),r=e.filter(m=>K.has(m.status)?!1:!n.has(o(m))),i=t.filter(m=>!s.has(o(m))),c=t.filter(m=>s.has(o(m))),l=e.filter(m=>K.has(m.status)),a=[...r,...c,...i],u=[];for(let m of a){let g=le(m.text),p=le(m.sender),f=!1;for(let h=0;h<u.length;h++){let S=u[h];if(le(S.sender)!==p)continue;let y=le(S.text);if(g.length>y.length&&g.endsWith(y)){f=!0;break}if(y.length>g.length&&y.endsWith(g)){u[h]=m,f=!0;break}}f||u.push(m)}return[...u,...l]}async function ke(e,t){let o=await e.evaluate(i=>{let c=document.querySelectorAll(".msg-conversation-listitem");for(let l of c)if(l.querySelector(".msg-conversation-listitem__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")===i){let d=l.getBoundingClientRect();if(d.height>0)return{x:d.x+d.width/2,y:d.y+d.height/2}}return null},t);if(o){await e.mouse.click(o.x,o.y),await E(3e3);let i=await Je(e,t);if(i.verified)return i}let n=vt(t),s=n!==t?[t,n]:[t];for(let i of s)if(await qt(e,i,t)){let l=await Je(e,t);if(l.verified)return l}let r=bt(n);if(r!==n&&r.length>0&&await qt(e,r,t,{requireUnique:!0})){let c=await Je(e,t);if(c.verified)return c}return{verified:!1,headerName:null}}async function qt(e,t,o,n={}){let{requireUnique:s=!1}=n,r=await e.$("input#search-conversations, .msg-cross-pillar-search-form__search-field");if(!r)return!1;await r.click(),await E(300),await e.keyboard.down("Control"),await e.keyboard.press("a"),await e.keyboard.up("Control"),await e.keyboard.press("Backspace"),await E(300),await r.type(t,{delay:30}),await E(500),await e.keyboard.press("Enter"),await E(3500);let i=await e.evaluate((c,l)=>{let a=c.split(" ")[0].toLowerCase(),u=c.toLowerCase(),d=document.querySelectorAll(".msg-conversation-listitem"),m=[],g=[];for(let f of d){let S=f.querySelector(".msg-conversation-listitem__participant-names, .msg-conversation-card__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")||"",y=S.toLowerCase();if(y.includes(a)){let $=f.getBoundingClientRect();if($.height>0){let w=S.includes(",")||/\d+ other/.test(S),R=y===u?3:y.includes(u)?2:1,_={text:S,isGroup:w,score:R,x:$.x+$.width/2,y:$.y+$.height/2};w?g.push(_):m.push(_)}}}m.sort((f,h)=>h.score-f.score);let p=[...m,...g];return l&&p.length!==1?null:m[0]||g[0]||null},o,s);return i?(s&&console.log(" (found via stripped designations)"),await e.mouse.click(i.x,i.y),!0):!1}async function Yt(e){let t=await e.$("input#search-conversations, .msg-cross-pillar-search-form__search-field");t&&(await t.click(),await E(200),await e.keyboard.down("Control"),await e.keyboard.press("a"),await e.keyboard.up("Control"),await e.keyboard.press("Backspace"),await e.keyboard.press("Escape"),await E(1e3))}import"dotenv/config";import*as v from"fs";import*as Ze from"os";import*as x from"path";import{fileURLToPath as Bn}from"url";var Ye={};ae(Ye,{NAME:()=>sn,VERSION:()=>nn,up:()=>cn});import*as U from"fs";import*as Ee from"path";var nn=1,sn="normalize-jobs-and-questions",rn=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],ln=50;function He(e){return U.existsSync(e)?JSON.parse(U.readFileSync(e,"utf-8")):null}function Ve(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{U.writeFileSync(n,o)}catch(s){try{U.unlinkSync(n)}catch{}throw s}try{U.renameSync(n,e)}catch(s){try{U.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function cn(e,t){let o=Ee.join(e,"jobs.json"),n=Ee.join(t,"scouted-jobs.json"),s=Ee.join(e,"application-questions.json"),r=He(o)||{},i=He(n)||{},c=0,l=[];for(let u of Object.keys(r)){if(!i[u])continue;let d=i[u],m=r[u];for(let g of rn)d[g]!=null&&m[g]==null&&(m[g]=d[g]);l.push(u),c++}c>0?(Ve(o,r),console.log(` Phase 1: Merged ${c} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let a=He(s);if(a&&Array.isArray(a.applications)){let u={},d=0;for(let m of a.applications){let g=m.jobId||"unknown";u[g]||(u[g]={applications:[]});let p=(m.questions||[]).map(f=>{let h={type:f.type||"input",question:f.question||"",answer:f.answer||""};return f.options&&(f.options.length<=ln?h.options=f.options:(h.optionsOmitted=!0,d++)),f.page&&(h.page=f.page),f.wasPreFilled&&(h.wasPreFilled=!0),f.wasRetry&&(h.wasRetry=!0),h});u[g].applications.push({applicationId:m.applicationId,date:m.date,result:m.result||"submitted",formPages:m.formPages||null,duration:m.duration||null,retries:m.retries||0,failedField:m.failedField||null,questionCount:m.questionCount||p.length,questions:p})}Ve(s,u),console.log(` Phase 2: Rekeyed ${a.applications.length} applications by jobId (${Object.keys(u).length} unique jobs)`),d>0&&console.log(` Phase 2: Stripped bloated options from ${d} 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 u of l)delete i[u];Ve(n,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 Ge={};ae(Ge,{NAME:()=>un,VERSION:()=>an,up:()=>pn});import*as B from"fs";import*as Gt from"path";var an=2,un="reset-unread-flags";function fn(e){return B.existsSync(e)?JSON.parse(B.readFileSync(e,"utf-8")):null}function dn(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{B.writeFileSync(n,o)}catch(s){try{B.unlinkSync(n)}catch{}throw s}try{B.renameSync(n,e)}catch(s){try{B.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function pn(e){let t=Gt.join(e,"conversations.json"),o=fn(t);if(!o){console.log(" conversations.json not found \u2014 nothing to reset.");return}let n=0;for(let s of Object.keys(o))o[s].unread===!0&&(o[s].unread=!1,n++);if(n===0){console.log(" No stale unread flags found \u2014 nothing to do.");return}dn(t,o),console.log(` Reset unread=false on ${n} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var ze={};ae(ze,{NAME:()=>gn,VERSION:()=>mn,up:()=>vn});import*as q from"fs";import*as zt from"path";var mn=3,gn="recompute-last-message-by",hn=new Set(["draft","confirmed","rejected","needs_review"]);function yn(e){return q.existsSync(e)?JSON.parse(q.readFileSync(e,"utf-8")):null}function wn(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{q.writeFileSync(n,o)}catch(s){try{q.unlinkSync(n)}catch{}throw s}try{q.renameSync(n,e)}catch(s){try{q.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}function Sn(e,t){if(!e||e.length===0)return"them";let o=e.filter(r=>!hn.has(r.status));if(o.length===0)return"them";let n=o.map(r=>({m:r,t:Date.parse(r.time)})).filter(r=>Number.isFinite(r.t)).sort((r,i)=>i.t-r.t).map(r=>r.m),s=n.length>0?n:[...o].reverse();for(let r of s){let i=r.sender;if(i===t)return"me";if(i!=="Unknown"&&i!=="")return"them"}return"them"}async function vn(e){let t=zt.join(e,"conversations.json"),o=yn(t);if(!o){console.log(" conversations.json not found \u2014 nothing to recompute.");return}let n=process.env.LINKEDIN_NAME;if(!n||typeof n!="string"||!n.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 s=0,r=0,i=0;for(let c of Object.keys(o)){let l=o[c];if(!l||typeof l!="object")continue;Object.prototype.hasOwnProperty.call(l,"_cardSynced")&&(delete l._cardSynced,r++);let a=Array.isArray(l.messages)?l.messages:[],u=Sn(a,n),d=u==="me"?"awaiting_reply":"unanswered_by_us";l.lastMessageBy!==u&&i++,l.lastMessageBy=u,l.conversationStatus=d,s++}if(s===0){console.log(" No conversations to recompute.");return}wn(t,o),console.log(` Recomputed lastMessageBy + conversationStatus on ${s} conversations.`),console.log(` ${i} had stale lastMessageBy (now corrected).`),r>0&&console.log(` Cleaned ${r} leftover _cardSynced flags.`),console.log(" Re-run 'ilml linkedin enrich' if you want actionNeeded / summary / autoTags to also update.")}var Ke={};ae(Ke,{NAME:()=>$n,VERSION:()=>bn,up:()=>Cn});import*as J from"fs";import*as Kt from"path";var bn=4,$n="split-connection-timestamps",kn=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function En(e){if(!J.existsSync(e))return null;let t=J.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function An(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{J.writeFileSync(n,o)}catch(s){try{J.unlinkSync(n)}catch{}throw s}try{J.renameSync(n,e)}catch(s){try{J.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function Cn(e){let t=Kt.join(e,"people.json"),o=En(t);if(!o||typeof o!="object"){console.log(" people.json not found \u2014 nothing to migrate.");return}let n=0,s=0;for(let r of Object.keys(o)){let i=o[r];!i||typeof i!="object"||(s++,kn.has(i.connectionStatus)&&i.connectionDate&&!i.requestedConnectionAt&&(i.requestedConnectionAt=i.connectionDate,i.connectionDate=null,n++))}if(n===0){console.log(` Scanned ${s} people \u2014 none needed the connectionDate\u2192requestedConnectionAt move.`);return}An(t,o),console.log(` Moved connectionDate \u2192 requestedConnectionAt on ${n} still-pending people (of ${s} scanned).`),console.log(' connectionDate now means "accepted on" only; requestedConnectionAt means "requested on".')}var Qe={};ae(Qe,{NAME:()=>_n,VERSION:()=>xn,up:()=>Tn});import*as W from"fs";import*as Qt from"path";var xn=5,_n="rename-headline-to-title",te=/[·•]\s*(1st|2nd|3rd)\+?/i,Rn=e=>(e||"").replace(/\s+/g," ").trim(),me=e=>Rn(e).replace(/^[+·•\s]+/,"").replace(/[·•\s]+$/,"");function Dn(e){if(!W.existsSync(e))return null;let t=W.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function In(e,t){let o=JSON.stringify(t),n=e+".tmp";try{W.writeFileSync(n,o)}catch(s){try{W.unlinkSync(n)}catch{}throw s}try{W.renameSync(n,e)}catch(s){try{W.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function Tn(e,t){let o=Qt.join(t,"discovered-people.json"),n=Dn(o);if(!n||typeof n!="object"){console.log(" discovered-people.json not found \u2014 nothing to migrate.");return}let s=Array.isArray(n)?n:Object.values(n),r=0,i=0,c=0;for(let l of s)if(!(!l||typeof l!="object")){if(r++,l.name&&te.test(l.name)){let a=me(String(l.name).split(te)[0]),u=String(l.name).match(/[·•]\s*(?:1st|2nd|3rd)\+?\s*(.+)$/i),d=u?me(u[1]):"";a&&(l.name=a,i++),d&&(!l.headline||te.test(String(l.headline))||!me(l.headline))&&(l.headline=d)}if(Object.prototype.hasOwnProperty.call(l,"headline")){let a=me(l.headline||"");te.test(a)&&(a=""),(!l.title||te.test(String(l.title)))&&(l.title=a||(l.title&&!te.test(String(l.title))?l.title:null)||null),delete l.headline,c++}else if(typeof l.title=="string"){let a=me(l.title);a&&!te.test(a)&&a!==l.title&&(l.title=a)}}if(c===0&&i===0){console.log(` Scanned ${r} discovered people \u2014 already unified (no headline field, clean names).`);return}In(o,n),console.log(` discovered-people.json: scanned ${r}, de-glued ${i} names, moved ${c} headline\u2192title.`),console.log(" Every record now carries a single `title` field (matches people.json).")}import"dotenv/config";import*as F from"path";import{fileURLToPath as On}from"url";var jn=On(import.meta.url),Nn=F.dirname(F.dirname(jn)),ge=process.env.DATA_DIR?F.resolve(process.env.DATA_DIR,"market-research"):F.join(Nn,"market-research"),gr=F.join(ge,"scouted-jobs.json"),hr=F.join(ge,"discovered-people.json"),yr=F.join(ge,"visit-log.json"),wr=F.join(ge,"companies.json"),Sr=F.join(ge,"job-decisions.json");function Xt(){Mn=null,Pn=null,Fn=null,Ln=null,Un=null}var Mn=null;var Pn=null;var Fn=null;var Ln=null;var Un=null;var qn=Bn(import.meta.url),Jn=x.dirname(qn),Wn=x.dirname(x.dirname(Jn));function Hn(e){return e&&(e==="~"?Ze.homedir():e.startsWith("~/")||e.startsWith("~\\")?x.join(Ze.homedir(),e.slice(2)):e)}var H=process.env.DATA_DIR?x.resolve(Hn(process.env.DATA_DIR)):Wn,ne=x.join(H,"collected-profiles"),se=x.join(H,"market-research"),N=x.join(H,".schema-version"),Zt=[x.join(ne,".schema-version"),x.join(se,".schema-version")],X=5,z=[{version:1,module:Ye},{version:2,module:Ge},{version:3,module:ze},{version:4,module:Ke},{version:5,module:Qe}];(function(){z.sort((n,s)=>n.version-s.version);let t=new Set;for(let n of z){if(!Number.isInteger(n.version)||n.version<1)throw new Error(`MIGRATION_REGISTRY: invalid version ${JSON.stringify(n.version)} (must be positive integer)`);if(t.has(n.version))throw new Error(`MIGRATION_REGISTRY: duplicate version v${n.version}`);t.add(n.version);let s=n.module;if(!s||typeof s.up!="function")throw new Error(`MIGRATION_REGISTRY: v${n.version} module is missing up()`);if(s.VERSION!==void 0&&s.VERSION!==n.version)throw new Error(`MIGRATION_REGISTRY: v${n.version} entry does not match module VERSION (${s.VERSION}). Drift between registry and module \u2014 pick one source of truth.`)}let o=z.length?z[z.length-1].version:0;if(o!==X)throw new Error(`MIGRATION_REGISTRY: highest version v${o} but CURRENT_SCHEMA_VERSION=${X}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let n=0;n<z.length;n++){let s=n+1;if(z[n].version!==s)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${s} at position ${n} but found v${z[n].version}`)}})();function no(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
|
|
9
|
+
\u2717 ${e}${m} failed: ${l.message||l}`)}let u=Date.now()-i.startTime,d=!l&&c.stoppedReason?String(c.stoppedReason):null;try{Ho(i,l?"error":d?"partial":"success",{...c,summaryLines:a,__deferReport:!0},l?l.message||String(l):null)}catch(m){console.error(`[runLog] endRun failed: ${m.message}`)}if(a.length>0&&process.env.NODE_RUN_REPORTS){let m=l?"error":d?"stopped":c.dailyLimitHit?"quota-reached":"done";try{await Fe({scriptName:i.script,mode:i.mode,status:m,summaryLines:a,error:l?l.message||String(l):null,stoppedReason:d,duration:u})}catch(g){console.error(`[runLog] report post failed: ${g.message}`)}}if(l&&r)throw l;return c}import Xo from"puppeteer";import{addExtra as Zo}from"puppeteer-extra";import en from"puppeteer-extra-plugin-stealth";import Be from"fs";import Vo from"path";var Yo={headless:!1,defaultViewport:null,protocolTimeout:3e5},pe=new Set,Ot=!1;async function be(){let e=[...pe].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});pe.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function Go(){if(Ot)return;Ot=!0;let e=t=>async()=>{await be(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await be(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await be(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await be(),process.exit(0))}),process.on("exit",()=>{for(let t of pe)try{let o=t.process&&t.process();o&&o.kill("SIGKILL")}catch{}})}function qe(e){return Vo.join(e,".ilml-browser-session.json")}function Pt(e,t={}){try{Be.writeFileSync(qe(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function zo(e,t){try{let o=t.wsEndpoint&&t.wsEndpoint();Pt(e,o?{wsEndpoint:o}:{})}catch{}}function Ko(e){let t=Ft(e);t&&t.ownerPid===process.pid&&de(e)}function de(e){try{Be.rmSync(qe(e),{force:!0})}catch{}}function Ft(e){try{return JSON.parse(Be.readFileSync(qe(e),"utf-8"))}catch{return null}}function jt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Nt(e,t,o){let n,s=new Promise(i=>{n=setTimeout(()=>i(o),t)}),r=Promise.resolve(e).then(i=>(clearTimeout(n),i),i=>{throw clearTimeout(n),i});return Promise.race([r,s])}async function Mt(e,t,{probeTimeoutMs:o=4e3}={}){let n=Ft(t);if(!n)return"free";if(!n.wsEndpoint)return n.ownerPid&&n.ownerPid!==process.pid&&jt(n.ownerPid)?"live":(de(t),"free");let s=Symbol("timeout"),r=e.connect({browserWSEndpoint:n.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Nt(r,o,s);if(i===s)return r.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return de(t),"free";if(jt(n.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Nt(i.close().then(()=>!0).catch(()=>!1),o,!1))return de(t),"free";try{i.disconnect()}catch{}return"live"}function Qo(e,t){e&&(Go(),pe.add(e),e.on("disconnected",()=>{pe.delete(e),t&&de(t)}))}async function Lt(e,t={}){let o=dt(),n={...Yo,...t,userDataDir:o},s=`Could not open the LinkedIn automation Chrome profile at ${o}. 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,o)==="live")throw new Error(s);Pt(o);let r;try{r=await e.launch(n)}catch{if(await Mt(e,o)==="live")throw new Error(s);ht(o);try{r=await e.launch(n)}catch(c){throw Ko(o),new Error(`Could not open the LinkedIn automation Chrome profile at ${o}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${c.message})`)}}return Qo(r,o),zo(o,r),{browser:r,profileDir:o,needsSeed:mt(o)}}async function Ut(e,t){if(!t||!t.needsSeed)return!1;if(!ut())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(...at()),gt(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Jt=Zo(Xo);Jt.use(en());async function Wt(){let{browser:e,profileDir:t,needsSeed:o}=await Lt(Jt),n=await e.newPage();return await n.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Ut(n,{profileDir:t,needsSeed:o}),console.log(" Browser launched (persistent profile)."),{browser:e,page:n}}async function We(e){await we(e,"https://www.linkedin.com/messaging/",{readySelector:".msg-conversations-container__conversations-list, .msg-conversation-listitem",label:"inbox"})||(console.warn(" Inbox container not found via known selectors, proceeding..."),await E(3e3)),console.log(" Inbox loaded.")}async function Ht(e){let t=await e.evaluate(()=>{let o=document.querySelectorAll(".msg-conversation-listitem"),n=[];for(let s of o){let i=s.querySelector(".msg-conversation-listitem__participant-names, .msg-conversation-card__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")||null;if(!i)continue;let l=s.querySelector(".msg-conversation-card__message-snippet")?.textContent?.trim()?.replace(/\s+/g," ")||"";if(/^\s*Sponsored\b/i.test(l)||/^\s*Promoted\b/i.test(l))continue;let u=s.querySelector(".msg-conversation-listitem__time-stamp, .msg-conversation-card__time-stamp")?.textContent?.trim()||"",d=s.querySelector(".msg-conversation-card__convo-item-container--unread")!==null||s.querySelector(".msg-conversation-card__unread-count")!==null,m,g;if(l.startsWith("You:"))m="me",g=l.slice(4).trim();else{let f=i.split(" ")[0];if(l.startsWith(i+":")||l.startsWith(f+":")){m="them";let h=l.indexOf(":");g=l.slice(h+1).trim()}else m="them",g=l}let p=s.getBoundingClientRect();n.push({name:i,lastMessagePreview:g,lastMessageTime:u,unread:d,lastMessageBy:m,x:p.x+p.width/2,y:p.y+p.height/2})}return n});for(let o of t)o.lastMessageTime=Oe(o.lastMessageTime)||o.lastMessageTime;return t}async function Vt(e){return e.evaluate(()=>{let t=[".msg-conversations-container__conversations-list",".msg-conversations-container",'[class*="msg-conversations-container"] ul'];for(let n of t){let s=document.querySelector(n);if(s&&s.scrollHeight>s.clientHeight)return s.scrollBy(0,600),{scrollTop:s.scrollTop,scrollHeight:s.scrollHeight}}let o=document.querySelector(".msg-conversation-listitem");if(o){let n=o.parentElement;for(;n;){if(n.scrollHeight>n.clientHeight+10)return n.scrollBy(0,600),{scrollTop:n.scrollTop,scrollHeight:n.scrollHeight};n=n.parentElement}}return null})}async function tn(e){return e.evaluate(()=>{let t=/^status\s+is\s+|^online$|^offline$|^away$|^busy$|^active\s/i,o=['.msg-overlay-conversation-bubble__header a[href*="/in/"] .truncate','.msg-overlay-conversation-bubble__header a[href*="/in/"]','.msg-overlay-bubble-header__title a[href*="/in/"]',".msg-overlay-bubble-header__title",".msg-overlay-conversation-bubble__header .truncate",".msg-thread__link-to-profile","h2.msg-entity-lockup__entity-title",".msg-s-message-list-container h2",".msg-thread h2",".msg-conversations-container__title-row h2",'.msg-thread a[href*="/in/"]'];for(let s of o){let r=document.querySelector(s);if(r){let i=r.textContent?.trim()?.replace(/\s+/g," ");if(i&&i.length>1&&i.length<100&&!t.test(i))return i}}let n=document.querySelectorAll(".msg-s-message-list-container h2, .msg-thread h2");for(let s of n){let r=s.textContent?.trim();if(r&&r.length>1&&r.length<100&&!/conversation/i.test(r)&&!t.test(r))return r}return null})}async function Bt(e,t){let o=await tn(e);if(!o)return{match:!1,headerName:null};let n=t.split(" ")[0].toLowerCase(),s=t.toLowerCase().split(/[\s,]+/).filter(c=>c.length>1),r=o.toLowerCase();return{match:r.includes(n)||s.some(c=>r.includes(c)),headerName:o}}async function Je(e,t){let o=t.split(" ")[0],n=8e3,s=500,r=0;for(;r<n;){let{match:l,headerName:a}=await Bt(e,t);if(l)return await E(500),{verified:!0,headerName:a};let u=await e.evaluate(()=>{let d=document.querySelectorAll(".msg-s-message-group__name"),m=Array.from(d).map(p=>p.textContent?.trim()).filter(Boolean),g=document.querySelectorAll(".msg-s-event-listitem").length;return{allSenders:m,msgCount:g}});if(u.msgCount>0&&u.allSenders.some(m=>m.toLowerCase().includes(o.toLowerCase())))return await E(300),{verified:!0,headerName:o};await E(s),r+=s}let{match:i,headerName:c}=await Bt(e,t);return i?{verified:!0,headerName:c}:{verified:!1,headerName:c}}async function on(e){let o=0,n=0;for(let s=0;s<50;s++){let r=await e.evaluate(()=>{let i=document.querySelector(".msg-thread .msg-s-message-list-container")||document.querySelector(".msg-s-message-list-container");if(!i){let a=document.querySelector(".msg-overlay-conversation-bubble");a&&(i=a.querySelector(".msg-s-message-list-container")||a.querySelector(".msg-s-message-list"))}if(!i)return{found:!1};let c=i.querySelectorAll("li.msg-s-message-list__event").length;return i.scrollTop<=1?{found:!0,atTop:!0,msgCount:c}:(i.scrollTop=0,{found:!0,atTop:!1,msgCount:c})});if(!r.found||r.atTop)break;if(await E(800),r.msgCount===o){if(n++,n>=3)break}else n=0;o=r.msgCount}}async function ce(e,{scrollUp:t=!0}={}){t&&await on(e);let o=await e.evaluate(()=>{let n=[],s="",r=document.querySelector(".msg-thread .msg-s-message-list-container")||document.querySelector(".msg-s-message-list-container");if(!r){let u=document.querySelector(".msg-overlay-conversation-bubble");u&&(r=u.querySelector(".msg-s-message-list-container")||u.querySelector(".msg-s-message-list"))}if(!r)return n;let i=r.querySelectorAll("li.msg-s-message-list__event");if(i.length===0){let u=document.querySelector(".msg-overlay-conversation-bubble, .msg-overlay-list-bubble");u&&(i=u.querySelectorAll("li.msg-s-message-list__event, .msg-s-event-listitem"))}function c(u){let d=u.getAttribute("data-event-urn");if(!d)return null;let m=d.match(/,2-([A-Za-z0-9+/=]+)\)/);if(!m)return null;try{let p=atob(m[1]).match(/^(\d{13})/);if(!p)return null;let f=Number(p[1]);if(f>14200704e5&&f<20512224e5)return f}catch{}return null}let l="";function a(u,d,m){let g=c(u),f=u.querySelector(".msg-s-message-group__timestamp, time")?.textContent?.trim()||"",h=u.querySelector(".msg-s-event-listitem__message-bubble");if(!h)return null;let S=h.textContent?.trim()?.replace(/Open Emoji Keyboard|Reply to this message|React with[^.]+/g,"")?.replace(/^(?:👏\s*👍\s*😊\s*)+/,"")?.replace(/\s+/g," ")?.trim()||"";if(!S)return null;let y=g?new Date(g).toISOString():m?`${m} ${f}`.trim():f;return{sender:d,text:S,time:y}}for(let u of i){let d=u.querySelector("time.msg-s-message-list__time-heading");d&&(l=d.textContent?.trim()||l);let m=u.querySelectorAll(".msg-s-message-group");if(m.length>0)for(let g of m){let p=g.querySelector(".msg-s-message-group__name");p&&(s=p.textContent?.trim()||s);let f=s||"Unknown",h=g.querySelectorAll(".msg-s-event-listitem");for(let S of h){let y=a(S,f,l);y&&n.push(y)}}else{let g=u.querySelectorAll(".msg-s-event-listitem");for(let p of g){let f=p.querySelector(".msg-s-message-group__name");f&&(s=f.textContent?.trim()||s);let S=a(p,s||"Unknown",l);S&&n.push(S)}}}if(n.length===0){let u=document.querySelectorAll(".msg-s-event-listitem--group-a11y-heading");for(let d of u){let m=d.textContent?.trim();m&&n.push({sender:"",text:m,time:""})}}return n});for(let n of o)(!n.time||!/^\d{4}-\d{2}-\d{2}T/.test(n.time))&&(n.time=Oe(n.time)||n.time);return o}function le(e){return String(e||"").replace(/\s+/g," ").trim().toLowerCase()}function $e(e,t){if(!e||e.length===0)return t||[];if(!t||t.length===0)return e;let o=m=>le(m.sender)+"||"+le(m.text).replace(/\s+/g,""),n=new Set(t.map(o)),s=new Set(e.map(o)),r=e.filter(m=>K.has(m.status)?!1:!n.has(o(m))),i=t.filter(m=>!s.has(o(m))),c=t.filter(m=>s.has(o(m))),l=e.filter(m=>K.has(m.status)),a=[...r,...c,...i],u=[];for(let m of a){let g=le(m.text),p=le(m.sender),f=!1;for(let h=0;h<u.length;h++){let S=u[h];if(le(S.sender)!==p||!S.time||!m.time||S.time!==m.time)continue;let y=le(S.text);if(g.length>y.length&&g.endsWith(y)){f=!0;break}if(y.length>g.length&&y.endsWith(g)){u[h]=m,f=!0;break}}f||u.push(m)}return[...u,...l]}async function ke(e,t){let o=await e.evaluate(i=>{let c=document.querySelectorAll(".msg-conversation-listitem");for(let l of c)if(l.querySelector(".msg-conversation-listitem__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")===i){let d=l.getBoundingClientRect();if(d.height>0)return{x:d.x+d.width/2,y:d.y+d.height/2}}return null},t);if(o){await e.mouse.click(o.x,o.y),await E(3e3);let i=await Je(e,t);if(i.verified)return i}let n=vt(t),s=n!==t?[t,n]:[t];for(let i of s)if(await qt(e,i,t)){let l=await Je(e,t);if(l.verified)return l}let r=bt(n);if(r!==n&&r.length>0&&await qt(e,r,t,{requireUnique:!0})){let c=await Je(e,t);if(c.verified)return c}return{verified:!1,headerName:null}}async function qt(e,t,o,n={}){let{requireUnique:s=!1}=n,r=await e.$("input#search-conversations, .msg-cross-pillar-search-form__search-field");if(!r)return!1;await r.click(),await E(300),await e.keyboard.down("Control"),await e.keyboard.press("a"),await e.keyboard.up("Control"),await e.keyboard.press("Backspace"),await E(300),await r.type(t,{delay:30}),await E(500),await e.keyboard.press("Enter"),await E(3500);let i=await e.evaluate((c,l)=>{let a=c.split(" ")[0].toLowerCase(),u=c.toLowerCase(),d=document.querySelectorAll(".msg-conversation-listitem"),m=[],g=[];for(let f of d){let S=f.querySelector(".msg-conversation-listitem__participant-names, .msg-conversation-card__participant-names")?.textContent?.trim()?.replace(/\s+/g," ")||"",y=S.toLowerCase();if(y.includes(a)){let $=f.getBoundingClientRect();if($.height>0){let w=S.includes(",")||/\d+ other/.test(S),R=y===u?3:y.includes(u)?2:1,_={text:S,isGroup:w,score:R,x:$.x+$.width/2,y:$.y+$.height/2};w?g.push(_):m.push(_)}}}m.sort((f,h)=>h.score-f.score);let p=[...m,...g];return l&&p.length!==1?null:m[0]||g[0]||null},o,s);return i?(s&&console.log(" (found via stripped designations)"),await e.mouse.click(i.x,i.y),!0):!1}async function Yt(e){let t=await e.$("input#search-conversations, .msg-cross-pillar-search-form__search-field");t&&(await t.click(),await E(200),await e.keyboard.down("Control"),await e.keyboard.press("a"),await e.keyboard.up("Control"),await e.keyboard.press("Backspace"),await e.keyboard.press("Escape"),await E(1e3))}import"dotenv/config";import*as v from"fs";import*as Ze from"os";import*as x from"path";import{fileURLToPath as Bn}from"url";var Ye={};ae(Ye,{NAME:()=>sn,VERSION:()=>nn,up:()=>cn});import*as U from"fs";import*as Ee from"path";var nn=1,sn="normalize-jobs-and-questions",rn=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],ln=50;function He(e){return U.existsSync(e)?JSON.parse(U.readFileSync(e,"utf-8")):null}function Ve(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{U.writeFileSync(n,o)}catch(s){try{U.unlinkSync(n)}catch{}throw s}try{U.renameSync(n,e)}catch(s){try{U.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function cn(e,t){let o=Ee.join(e,"jobs.json"),n=Ee.join(t,"scouted-jobs.json"),s=Ee.join(e,"application-questions.json"),r=He(o)||{},i=He(n)||{},c=0,l=[];for(let u of Object.keys(r)){if(!i[u])continue;let d=i[u],m=r[u];for(let g of rn)d[g]!=null&&m[g]==null&&(m[g]=d[g]);l.push(u),c++}c>0?(Ve(o,r),console.log(` Phase 1: Merged ${c} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let a=He(s);if(a&&Array.isArray(a.applications)){let u={},d=0;for(let m of a.applications){let g=m.jobId||"unknown";u[g]||(u[g]={applications:[]});let p=(m.questions||[]).map(f=>{let h={type:f.type||"input",question:f.question||"",answer:f.answer||""};return f.options&&(f.options.length<=ln?h.options=f.options:(h.optionsOmitted=!0,d++)),f.page&&(h.page=f.page),f.wasPreFilled&&(h.wasPreFilled=!0),f.wasRetry&&(h.wasRetry=!0),h});u[g].applications.push({applicationId:m.applicationId,date:m.date,result:m.result||"submitted",formPages:m.formPages||null,duration:m.duration||null,retries:m.retries||0,failedField:m.failedField||null,questionCount:m.questionCount||p.length,questions:p})}Ve(s,u),console.log(` Phase 2: Rekeyed ${a.applications.length} applications by jobId (${Object.keys(u).length} unique jobs)`),d>0&&console.log(` Phase 2: Stripped bloated options from ${d} 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 u of l)delete i[u];Ve(n,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 Ge={};ae(Ge,{NAME:()=>un,VERSION:()=>an,up:()=>pn});import*as B from"fs";import*as Gt from"path";var an=2,un="reset-unread-flags";function fn(e){return B.existsSync(e)?JSON.parse(B.readFileSync(e,"utf-8")):null}function dn(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{B.writeFileSync(n,o)}catch(s){try{B.unlinkSync(n)}catch{}throw s}try{B.renameSync(n,e)}catch(s){try{B.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function pn(e){let t=Gt.join(e,"conversations.json"),o=fn(t);if(!o){console.log(" conversations.json not found \u2014 nothing to reset.");return}let n=0;for(let s of Object.keys(o))o[s].unread===!0&&(o[s].unread=!1,n++);if(n===0){console.log(" No stale unread flags found \u2014 nothing to do.");return}dn(t,o),console.log(` Reset unread=false on ${n} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var ze={};ae(ze,{NAME:()=>gn,VERSION:()=>mn,up:()=>vn});import*as q from"fs";import*as zt from"path";var mn=3,gn="recompute-last-message-by",hn=new Set(["draft","confirmed","rejected","needs_review"]);function yn(e){return q.existsSync(e)?JSON.parse(q.readFileSync(e,"utf-8")):null}function wn(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{q.writeFileSync(n,o)}catch(s){try{q.unlinkSync(n)}catch{}throw s}try{q.renameSync(n,e)}catch(s){try{q.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}function Sn(e,t){if(!e||e.length===0)return"them";let o=e.filter(r=>!hn.has(r.status));if(o.length===0)return"them";let n=o.map(r=>({m:r,t:Date.parse(r.time)})).filter(r=>Number.isFinite(r.t)).sort((r,i)=>i.t-r.t).map(r=>r.m),s=n.length>0?n:[...o].reverse();for(let r of s){let i=r.sender;if(i===t)return"me";if(i!=="Unknown"&&i!=="")return"them"}return"them"}async function vn(e){let t=zt.join(e,"conversations.json"),o=yn(t);if(!o){console.log(" conversations.json not found \u2014 nothing to recompute.");return}let n=process.env.LINKEDIN_NAME;if(!n||typeof n!="string"||!n.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 s=0,r=0,i=0;for(let c of Object.keys(o)){let l=o[c];if(!l||typeof l!="object")continue;Object.prototype.hasOwnProperty.call(l,"_cardSynced")&&(delete l._cardSynced,r++);let a=Array.isArray(l.messages)?l.messages:[],u=Sn(a,n),d=u==="me"?"awaiting_reply":"unanswered_by_us";l.lastMessageBy!==u&&i++,l.lastMessageBy=u,l.conversationStatus=d,s++}if(s===0){console.log(" No conversations to recompute.");return}wn(t,o),console.log(` Recomputed lastMessageBy + conversationStatus on ${s} conversations.`),console.log(` ${i} had stale lastMessageBy (now corrected).`),r>0&&console.log(` Cleaned ${r} leftover _cardSynced flags.`),console.log(" Re-run 'ilml linkedin enrich' if you want actionNeeded / summary / autoTags to also update.")}var Ke={};ae(Ke,{NAME:()=>$n,VERSION:()=>bn,up:()=>Cn});import*as J from"fs";import*as Kt from"path";var bn=4,$n="split-connection-timestamps",kn=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]);function En(e){if(!J.existsSync(e))return null;let t=J.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function An(e,t){let o=JSON.stringify(t,null,2),n=e+".tmp";try{J.writeFileSync(n,o)}catch(s){try{J.unlinkSync(n)}catch{}throw s}try{J.renameSync(n,e)}catch(s){try{J.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function Cn(e){let t=Kt.join(e,"people.json"),o=En(t);if(!o||typeof o!="object"){console.log(" people.json not found \u2014 nothing to migrate.");return}let n=0,s=0;for(let r of Object.keys(o)){let i=o[r];!i||typeof i!="object"||(s++,kn.has(i.connectionStatus)&&i.connectionDate&&!i.requestedConnectionAt&&(i.requestedConnectionAt=i.connectionDate,i.connectionDate=null,n++))}if(n===0){console.log(` Scanned ${s} people \u2014 none needed the connectionDate\u2192requestedConnectionAt move.`);return}An(t,o),console.log(` Moved connectionDate \u2192 requestedConnectionAt on ${n} still-pending people (of ${s} scanned).`),console.log(' connectionDate now means "accepted on" only; requestedConnectionAt means "requested on".')}var Qe={};ae(Qe,{NAME:()=>_n,VERSION:()=>xn,up:()=>Tn});import*as W from"fs";import*as Qt from"path";var xn=5,_n="rename-headline-to-title",te=/[·•]\s*(1st|2nd|3rd)\+?/i,Rn=e=>(e||"").replace(/\s+/g," ").trim(),me=e=>Rn(e).replace(/^[+·•\s]+/,"").replace(/[·•\s]+$/,"");function Dn(e){if(!W.existsSync(e))return null;let t=W.readFileSync(e,"utf-8");return t.charCodeAt(0)===65279&&(t=t.slice(1)),JSON.parse(t)}function In(e,t){let o=JSON.stringify(t),n=e+".tmp";try{W.writeFileSync(n,o)}catch(s){try{W.unlinkSync(n)}catch{}throw s}try{W.renameSync(n,e)}catch(s){try{W.unlinkSync(n)}catch{}if(s.code==="EPERM"||s.code==="EBUSY"){let r=new Error(`File locked: ${e} (${s.code}). On Windows usually antivirus / file explorer / open editor. Close it and re-run.`);throw r.code=s.code,r}throw s}}async function Tn(e,t){let o=Qt.join(t,"discovered-people.json"),n=Dn(o);if(!n||typeof n!="object"){console.log(" discovered-people.json not found \u2014 nothing to migrate.");return}let s=Array.isArray(n)?n:Object.values(n),r=0,i=0,c=0;for(let l of s)if(!(!l||typeof l!="object")){if(r++,l.name&&te.test(l.name)){let a=me(String(l.name).split(te)[0]),u=String(l.name).match(/[·•]\s*(?:1st|2nd|3rd)\+?\s*(.+)$/i),d=u?me(u[1]):"";a&&(l.name=a,i++),d&&(!l.headline||te.test(String(l.headline))||!me(l.headline))&&(l.headline=d)}if(Object.prototype.hasOwnProperty.call(l,"headline")){let a=me(l.headline||"");te.test(a)&&(a=""),(!l.title||te.test(String(l.title)))&&(l.title=a||(l.title&&!te.test(String(l.title))?l.title:null)||null),delete l.headline,c++}else if(typeof l.title=="string"){let a=me(l.title);a&&!te.test(a)&&a!==l.title&&(l.title=a)}}if(c===0&&i===0){console.log(` Scanned ${r} discovered people \u2014 already unified (no headline field, clean names).`);return}In(o,n),console.log(` discovered-people.json: scanned ${r}, de-glued ${i} names, moved ${c} headline\u2192title.`),console.log(" Every record now carries a single `title` field (matches people.json).")}import"dotenv/config";import*as F from"path";import{fileURLToPath as On}from"url";var jn=On(import.meta.url),Nn=F.dirname(F.dirname(jn)),ge=process.env.DATA_DIR?F.resolve(process.env.DATA_DIR,"market-research"):F.join(Nn,"market-research"),gr=F.join(ge,"scouted-jobs.json"),hr=F.join(ge,"discovered-people.json"),yr=F.join(ge,"visit-log.json"),wr=F.join(ge,"companies.json"),Sr=F.join(ge,"job-decisions.json");function Xt(){Mn=null,Pn=null,Fn=null,Ln=null,Un=null}var Mn=null;var Pn=null;var Fn=null;var Ln=null;var Un=null;var qn=Bn(import.meta.url),Jn=x.dirname(qn),Wn=x.dirname(x.dirname(Jn));function Hn(e){return e&&(e==="~"?Ze.homedir():e.startsWith("~/")||e.startsWith("~\\")?x.join(Ze.homedir(),e.slice(2)):e)}var H=process.env.DATA_DIR?x.resolve(Hn(process.env.DATA_DIR)):Wn,ne=x.join(H,"collected-profiles"),se=x.join(H,"market-research"),N=x.join(H,".schema-version"),Zt=[x.join(ne,".schema-version"),x.join(se,".schema-version")],X=5,z=[{version:1,module:Ye},{version:2,module:Ge},{version:3,module:ze},{version:4,module:Ke},{version:5,module:Qe}];(function(){z.sort((n,s)=>n.version-s.version);let t=new Set;for(let n of z){if(!Number.isInteger(n.version)||n.version<1)throw new Error(`MIGRATION_REGISTRY: invalid version ${JSON.stringify(n.version)} (must be positive integer)`);if(t.has(n.version))throw new Error(`MIGRATION_REGISTRY: duplicate version v${n.version}`);t.add(n.version);let s=n.module;if(!s||typeof s.up!="function")throw new Error(`MIGRATION_REGISTRY: v${n.version} module is missing up()`);if(s.VERSION!==void 0&&s.VERSION!==n.version)throw new Error(`MIGRATION_REGISTRY: v${n.version} entry does not match module VERSION (${s.VERSION}). Drift between registry and module \u2014 pick one source of truth.`)}let o=z.length?z[z.length-1].version:0;if(o!==X)throw new Error(`MIGRATION_REGISTRY: highest version v${o} but CURRENT_SCHEMA_VERSION=${X}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let n=0;n<z.length;n++){let s=n+1;if(z[n].version!==s)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${s} at position ${n} but found v${z[n].version}`)}})();function no(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
|
|
10
10
|
[FATAL] DATA_DIR is set to whitespace ("${process.env.DATA_DIR}"). Either unset it or set a real path.
|
|
11
11
|
`),process.exit(1));try{v.mkdirSync(H,{recursive:!0})}catch(t){t.code!=="EEXIST"&&(console.error(`
|
|
12
12
|
[FATAL] Cannot create DATA_DIR (${H}): ${t.message}`),console.error(` Fix the path or permissions and try again.
|