ilml-plugin-linkedin 1.14.0 → 1.14.2
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/README.md +3 -1
- package/dist/applyQueue.mjs +24 -24
- package/dist/connect.mjs +81 -78
- package/dist/daily.mjs +16 -16
- package/dist/enrichProfile.mjs +9 -9
- package/dist/enrichProfiles.mjs +32 -32
- package/dist/funnelRunner.mjs +36 -36
- package/dist/messageBot.mjs +55 -55
- package/dist/migrationCli.mjs +16 -16
- package/dist/profileHistory.mjs +5 -5
- package/dist/quota.mjs +3 -3
- package/dist/run.mjs +36 -36
- package/dist/scout.mjs +19 -19
- package/dist/syncAll.mjs +64 -64
- package/dist/verifyAbout.mjs +11 -11
- package/dist/version.mjs +1 -1
- package/dist/viewers.mjs +22 -22
- package/dist/visit.mjs +26 -26
- package/dist/warmScan.mjs +3 -3
- package/ilml-plugin.json +14 -6
- package/package.json +1 -1
package/dist/migrationCli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Se=Object.defineProperty;var U=(e,o)=>{for(var n in o)Se(e,n,{get:o[n],enumerable:!0})};import"dotenv/config";import"dotenv/config";import*as i from"fs";import*as q from"os";import*as l from"path";import{fileURLToPath as oo}from"url";var W={};U(W,{NAME:()=>ve,VERSION:()=>be,up:()=>$e});import*as $ from"fs";import*as M from"path";var be=1,ve="normalize-jobs-and-questions",ke=["description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","companyUrl","scoutedAt","lastSeen"],we=50;function B(e){return $.existsSync(e)?JSON.parse($.readFileSync(e,"utf-8")):null}function V(e,o){let n=JSON.stringify(o,null,2),t=e+".tmp";try{$.writeFileSync(t,n)}catch(r){try{$.unlinkSync(t)}catch{}throw r}try{$.renameSync(t,e)}catch(r){try{$.unlinkSync(t)}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 $e(e,o){let n=M.join(e,"jobs.json"),t=M.join(o,"scouted-jobs.json"),r=M.join(e,"application-questions.json"),s=B(n)||{},c=B(t)||{},E=0,u=[];for(let f of Object.keys(s)){if(!c[f])continue;let b=c[f],d=s[f];for(let I of ke)b[I]!=null&&d[I]==null&&(d[I]=b[I]);u.push(f),E++}E>0?(V(n,s),console.log(` Phase 1: Merged ${E} scouted jobs into jobs.json`)):console.log(" Phase 1: No overlapping jobs to merge");let p=B(r);if(p&&Array.isArray(p.applications)){let f={},b=0;for(let d of p.applications){let I=d.jobId||"unknown";f[I]||(f[I]={applications:[]});let N=(d.questions||[]).map(g=>{let v={type:g.type||"input",question:g.question||"",answer:g.answer||""};return g.options&&(g.options.length<=we?v.options=g.options:(v.optionsOmitted=!0,b++)),g.page&&(v.page=g.page),g.wasPreFilled&&(v.wasPreFilled=!0),g.wasRetry&&(v.wasRetry=!0),v});f[I].applications.push({applicationId:d.applicationId,date:d.date,result:d.result||"submitted",formPages:d.formPages||null,duration:d.duration||null,retries:d.retries||0,failedField:d.failedField||null,questionCount:d.questionCount||N.length,questions:N})}V(r,f),console.log(` Phase 2: Rekeyed ${p.applications.length} applications by jobId (${Object.keys(f).length} unique jobs)`),b>0&&console.log(` Phase 2: Stripped bloated options from ${b} questions`)}else p&&!p.applications?console.log(" Phase 2: application-questions.json already in keyed format"):console.log(" Phase 2: No application-questions.json found");if(u.length>0){for(let f of u)delete c[f];V(t,c),console.log(` Phase 3: Removed ${u.length} merged entries from scouted-jobs.json (${Object.keys(c).length} remaining)`)}else console.log(" Phase 3: No entries to remove from scouted-jobs.json")}var Y={};U(Y,{NAME:()=>Ae,VERSION:()=>je,up:()=>Oe});import*as j from"fs";import*as K from"path";var je=2,Ae="reset-unread-flags";function Ee(e){return j.existsSync(e)?JSON.parse(j.readFileSync(e,"utf-8")):null}function Ie(e,o){let n=JSON.stringify(o,null,2),t=e+".tmp";try{j.writeFileSync(t,n)}catch(r){try{j.unlinkSync(t)}catch{}throw r}try{j.renameSync(t,e)}catch(r){try{j.unlinkSync(t)}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 Oe(e){let o=K.join(e,"conversations.json"),n=Ee(o);if(!n){console.log(" conversations.json not found \u2014 nothing to reset.");return}let t=0;for(let r of Object.keys(n))n[r].unread===!0&&(n[r].unread=!1,t++);if(t===0){console.log(" No stale unread flags found \u2014 nothing to do.");return}Ie(o,n),console.log(` Reset unread=false on ${t} conversations. Run 'ilml linkedin sync-all' to re-establish real unread state from LinkedIn.`)}var G={};U(G,{NAME:()=>Te,VERSION:()=>Re,up:()=>_e});import*as A from"fs";import*as Q from"path";var Re=3,Te="recompute-last-message-by",Ne=new Set(["draft","confirmed","rejected","needs_review"]);function De(e){return A.existsSync(e)?JSON.parse(A.readFileSync(e,"utf-8")):null}function Ce(e,o){let n=JSON.stringify(o,null,2),t=e+".tmp";try{A.writeFileSync(t,n)}catch(r){try{A.unlinkSync(t)}catch{}throw r}try{A.renameSync(t,e)}catch(r){try{A.unlinkSync(t)}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 xe(e,o){if(!e||e.length===0)return"them";let n=e.filter(s=>!Ne.has(s.status));if(n.length===0)return"them";let t=n.map(s=>({m:s,t:Date.parse(s.time)})).filter(s=>Number.isFinite(s.t)).sort((s,c)=>c.t-s.t).map(s=>s.m),r=t.length>0?t:[...n].reverse();for(let s of r){let c=s.sender;if(c===o)return"me";if(c!=="Unknown"&&c!=="")return"them"}return"them"}async function _e(e){let o=Q.join(e,"conversations.json"),n=De(o);if(!n){console.log(" conversations.json not found \u2014 nothing to recompute.");return}let t=process.env.LINKEDIN_NAME;if(!t||typeof t!="string"||!t.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,c=0;for(let E of Object.keys(n)){let u=n[E];if(!u||typeof u!="object")continue;Object.prototype.hasOwnProperty.call(u,"_cardSynced")&&(delete u._cardSynced,s++);let p=Array.isArray(u.messages)?u.messages:[],f=xe(p,t),b=f==="me"?"awaiting_reply":"unanswered_by_us";u.lastMessageBy!==f&&c++,u.lastMessageBy=f,u.conversationStatus=b,r++}if(r===0){console.log(" No conversations to recompute.");return}Ce(o,n),console.log(` Recomputed lastMessageBy + conversationStatus on ${r} conversations.`),console.log(` ${c} 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.")}import{getPluginConfig as Fe}from"@ilivemylife/graph-sdk";var Le=await Fe();for(let[e,o]of Object.entries(Le))process.env[e]===void 0&&(process.env[e]=o);var bo=process.env.LINKEDIN_NAME,D=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),vo=Object.freeze(new Set([D.DRAFT,D.CONFIRMED,D.NEEDS_REVIEW])),ko=Object.freeze(new Set([D.DRAFT,D.CONFIRMED,D.REJECTED,D.NEEDS_REVIEW]));import"dotenv/config";import*as y from"path";import{fileURLToPath as Me}from"url";var Pe=Me(import.meta.url),Je=y.dirname(y.dirname(Pe)),C=process.env.DATA_DIR?y.resolve(process.env.DATA_DIR,"collected-profiles"):y.join(Je,"collected-profiles"),Eo=y.join(C,"people.json"),Io=y.join(C,"jobs.json"),Oo=y.join(C,"quota.json"),Ro=y.join(C,"conversations.json"),To=y.join(C,"sync-state.json"),No=y.join(C,"application-questions.json"),Do=y.join(C,"profile-history"),X=(e,o)=>{let n=parseInt(e,10);return Number.isInteger(n)&&n>=0?n:o},Co=X(process.env.CONNECT_MAX_DAILY,20),xo=X(process.env.CONNECT_MAX_WEEKLY,100),Ue=null,Be=null,Ve=null,We=null,Ye=null,Ge=null;function Z(){Ue=null,Be=null,Ve=null,We=null,Ye=null,Ge=null}import"dotenv/config";import*as k from"path";import{fileURLToPath as qe}from"url";var He=qe(import.meta.url),ze=k.dirname(k.dirname(He)),F=process.env.DATA_DIR?k.resolve(process.env.DATA_DIR,"market-research"):k.join(ze,"market-research"),Mo=k.join(F,"scouted-jobs.json"),Po=k.join(F,"discovered-people.json"),Jo=k.join(F,"visit-log.json"),Uo=k.join(F,"companies.json"),Bo=k.join(F,"job-decisions.json");function ee(){Ke=null,Qe=null,Xe=null,Ze=null,eo=null}var Ke=null;var Qe=null;var Xe=null;var Ze=null;var eo=null;var no=oo(import.meta.url),to=l.dirname(no),so=l.dirname(l.dirname(to));function ro(e){return e&&(e==="~"?q.homedir():e.startsWith("~/")||e.startsWith("~\\")?l.join(q.homedir(),e.slice(2)):e)}var S=process.env.DATA_DIR?l.resolve(ro(process.env.DATA_DIR)):so,R=l.join(S,"collected-profiles"),T=l.join(S,"market-research"),m=l.join(S,".schema-version"),oe=[l.join(R,".schema-version"),l.join(T,".schema-version")],P=3,O=[{version:1,module:W},{version:2,module:Y},{version:3,module:G}];(function(){O.sort((t,r)=>t.version-r.version);let o=new Set;for(let t of O){if(!Number.isInteger(t.version)||t.version<1)throw new Error(`MIGRATION_REGISTRY: invalid version ${JSON.stringify(t.version)} (must be positive integer)`);if(o.has(t.version))throw new Error(`MIGRATION_REGISTRY: duplicate version v${t.version}`);o.add(t.version);let r=t.module;if(!r||typeof r.up!="function")throw new Error(`MIGRATION_REGISTRY: v${t.version} module is missing up()`);if(r.VERSION!==void 0&&r.VERSION!==t.version)throw new Error(`MIGRATION_REGISTRY: v${t.version} entry does not match module VERSION (${r.VERSION}). Drift between registry and module \u2014 pick one source of truth.`)}let n=O.length?O[O.length-1].version:0;if(n!==P)throw new Error(`MIGRATION_REGISTRY: highest version v${n} but CURRENT_SCHEMA_VERSION=${P}. Forgot to bump CURRENT_SCHEMA_VERSION when adding the new migration?`);for(let t=0;t<O.length;t++){let r=t+1;if(O[t].version!==r)throw new Error(`MIGRATION_REGISTRY: gap detected \u2014 expected v${r} at position ${t} but found v${O[t].version}`)}})();function re(){process.env.DATA_DIR!==void 0&&process.env.DATA_DIR.trim()===""&&process.env.DATA_DIR.length>0&&(console.error(`
|
|
2
2
|
[FATAL] DATA_DIR is set to whitespace ("${process.env.DATA_DIR}"). Either unset it or set a real path.
|
|
3
3
|
`),process.exit(1));try{i.mkdirSync(S,{recursive:!0})}catch(o){o.code!=="EEXIST"&&(console.error(`
|
|
4
4
|
[FATAL] Cannot create DATA_DIR (${S}): ${o.message}`),console.error(` Fix the path or permissions and try again.
|
|
@@ -8,30 +8,30 @@ var ye=Object.defineProperty;var U=(e,o)=>{for(var n in o)ye(e,n,{get:o[n],enume
|
|
|
8
8
|
[FATAL] DATA_DIR (${S}) 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.
|
|
9
9
|
`),process.exit(1));for(let o of[R,T]){let n;try{n=i.lstatSync(o)}catch(t){if(t.code==="ENOENT")continue;throw t}n.isSymbolicLink()&&(console.error(`
|
|
10
10
|
[FATAL] Data subdirectory is a symbolic link: ${o}`),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.
|
|
11
|
-
`),process.exit(1))}}var x=l.join(S,".migration-lock");function
|
|
11
|
+
`),process.exit(1))}}var x=l.join(S,".migration-lock");function ie(){let e=JSON.stringify({pid:process.pid,startedAt:new Date().toISOString()},null,2);try{return i.writeFileSync(x,e,{flag:"wx"}),!0}catch(t){if(t.code!=="EEXIST")throw t}let o;try{o=JSON.parse(i.readFileSync(x,"utf-8"))}catch{o=null}if(o&&Number.isInteger(o.pid)){let t=!1;try{process.kill(o.pid,0),t=!0}catch(c){c.code==="EPERM"&&(t=!0)}let r=24*60*60*1e3,s=1/0;if(o.startedAt){let c=Date.parse(o.startedAt);Number.isNaN(c)||(s=Date.now()-c)}t&&s<r&&(console.error(`
|
|
12
12
|
[FATAL] Another plugin process is currently running migrations:`),console.error(` PID: ${o.pid}`),console.error(` Started at: ${o.startedAt||"(unknown)"}`),console.error(` Lock file: ${x}
|
|
13
13
|
`),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.
|
|
14
|
-
`),process.exit(1)),t&&s>=r&&console.log(` Lock holder PID ${o.pid} is alive but lock is older than 24h \u2014 assuming PID reuse, taking over.`)}console.log(` Stale migration lock from PID ${o?.pid??"?"} found \u2014 taking over.`);let n=x+".tmp";try{return i.writeFileSync(n,e),i.renameSync(n,x),!0}catch(t){try{i.unlinkSync(n)}catch{}throw new Error(`Failed to take over stale migration lock: ${t.message}`)}}function L(){try{if(JSON.parse(i.readFileSync(x,"utf-8")).pid!==process.pid)return;i.unlinkSync(x)}catch{}}var
|
|
14
|
+
`),process.exit(1)),t&&s>=r&&console.log(` Lock holder PID ${o.pid} is alive but lock is older than 24h \u2014 assuming PID reuse, taking over.`)}console.log(` Stale migration lock from PID ${o?.pid??"?"} found \u2014 taking over.`);let n=x+".tmp";try{return i.writeFileSync(n,e),i.renameSync(n,x),!0}catch(t){try{i.unlinkSync(n)}catch{}throw new Error(`Failed to take over stale migration lock: ${t.message}`)}}function L(){try{if(JSON.parse(i.readFileSync(x,"utf-8")).pid!==process.pid)return;i.unlinkSync(x)}catch{}}var ne=!1;function ce(){if(ne)return;ne=!0;let e=()=>{try{L()}catch{}process.exit(1)};for(let o of["SIGINT","SIGTERM","SIGHUP"])try{process.on(o,e)}catch{}}function io(){if(i.existsSync(m))return;let e=[],o=[];for(let s of oe)if(i.existsSync(s))try{e.push({file:s,schema:J(s)})}catch(c){o.push({file:s,err:c.message})}if(o.length>0){console.error(`
|
|
15
15
|
[FATAL] Legacy .schema-version file(s) corrupted \u2014 cannot determine current schema version:`);for(let s of o)console.error(` ${s.file}: ${s.err}`);console.error(`
|
|
16
16
|
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.
|
|
17
17
|
`),process.exit(1)}if(e.length===0)return;let n=e.map(s=>s.schema.version);if(new Set(n).size>1){console.error(`
|
|
18
18
|
[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.
|
|
19
|
-
`),process.exit(1)}let t=e[0].schema,r=m+".tmp";try{i.writeFileSync(r,JSON.stringify(t,null,2)),i.renameSync(r,m)}catch(s){try{i.unlinkSync(r)}catch{}throw s}for(let s of
|
|
19
|
+
`),process.exit(1)}let t=e[0].schema,r=m+".tmp";try{i.writeFileSync(r,JSON.stringify(t,null,2)),i.renameSync(r,m)}catch(s){try{i.unlinkSync(r)}catch{}throw s}for(let s of oe)if(i.existsSync(s))try{i.unlinkSync(s)}catch{}console.log(` Consolidated legacy .schema-version files \u2192 ${m}`)}function co(e){if(e===null||typeof e!="object"||Array.isArray(e))return null;let o=Number(e.version);return!Number.isFinite(o)||o<0||!Number.isInteger(o)?null:(e.version=o,Array.isArray(e.migrations)||(e.migrations=[]),e)}function J(e){let o=i.readFileSync(e,"utf-8");return o.charCodeAt(0)===65279&&(o=o.slice(1)),JSON.parse(o)}function H(){if(io(),!i.existsSync(m))return{version:0,migrations:[]};let e;try{e=J(m)}catch(n){if(n.code==="ENOENT")return{version:0,migrations:[]};console.error(`
|
|
20
20
|
[FATAL] ${m} 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.
|
|
21
|
-
`),process.exit(1)}let o=
|
|
21
|
+
`),process.exit(1)}let o=co(e);return o===null&&(console.error(`
|
|
22
22
|
[FATAL] ${m} 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.
|
|
23
|
-
`),process.exit(1)),o}function
|
|
23
|
+
`),process.exit(1)),o}function le(e){try{i.chmodSync(e,384)}catch{}}function lo(e){if(re(),i.existsSync(m))try{if(i.lstatSync(m).isSymbolicLink())throw new Error(`SCHEMA_FILE is a symbolic link: ${m}. 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 o=m+".tmp";try{i.writeFileSync(o,JSON.stringify(e,null,2)),le(o),i.renameSync(o,m)}catch(n){try{i.unlinkSync(o)}catch{}throw n}}function te(e,o){return i.existsSync(e)?i.readdirSync(e).filter(n=>n.endsWith(o)):[]}var ao=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function uo(e){let o=e.toLowerCase();return ao.some(n=>n.test(o))}var fo=6e4;function ae(){let e=Date.now(),o=[];for(let n of[S,R,T]){if(!i.existsSync(n))continue;let t;try{t=i.readdirSync(n)}catch{continue}for(let r of t){if(!uo(r))continue;let s=l.join(n,r);try{let c=i.statSync(s);if(!c.isFile()||e-c.mtimeMs<fo)continue;o.push({dir:n,file:r,fullPath:s})}catch{}}}return o}function po({verbose:e=!1}={}){let o=ae();if(o.length===0)return e&&console.log(" No orphan .tmp files found."),0;let n=0;for(let t of o)try{i.unlinkSync(t.fullPath),n++,e&&console.log(` Removed orphan: ${l.basename(t.dir)}/${t.file}`)}catch(r){console.warn(` [!] Could not remove orphan ${t.fullPath}: ${r.message}`)}return!e&&n>0&&console.log(` Cleaned up ${n} orphan .tmp file(s) from previous run.`),n}function ue(){let e=[];for(let o of[R,T])if(i.existsSync(o))for(let n of i.readdirSync(o))/\.pre-rollback-v\d+\.backup$/.test(n)&&e.push({dir:o,file:n,fullPath:l.join(o,n)});return e}function fe({removePreRollback:e=!0}={}){if(console.log(`
|
|
24
24
|
\u{1F4E6} Data cleanup
|
|
25
25
|
`),!i.existsSync(S)){console.log(` DATA_DIR not yet created (${S}) \u2014 nothing to clean.
|
|
26
|
-
`);return}
|
|
26
|
+
`);return}re();let o=!1;try{ce(),ie(),o=!0,process.on("exit",()=>{o&&L()})}catch(r){console.error(`
|
|
27
27
|
[ERROR] Could not acquire cleanup lock: ${r.message}
|
|
28
|
-
`),process.exit(1)}let n=
|
|
28
|
+
`),process.exit(1)}let n=po({verbose:!0}),t=0;if(e){let r=ue();if(r.length===0)console.log(" No pre-rollback snapshots found.");else for(let s of r)try{i.unlinkSync(s.fullPath),t++,console.log(` Removed pre-rollback snapshot: ${l.basename(s.dir)}/${s.file}`)}catch(c){console.warn(` [!] Could not remove ${s.fullPath}: ${c.message}`)}}o&&(L(),o=!1),console.log(`
|
|
29
29
|
Done. Removed ${n} orphan .tmp + ${t} pre-rollback snapshot(s).`),console.log(` NOTE: .pre-vNNN.backup files are kept \u2014 they're needed for 'rollback-data'.
|
|
30
|
-
`)}function
|
|
30
|
+
`)}function ho(){let e=[];for(let o of[R,T]){if(!i.existsSync(o))continue;let n=i.readdirSync(o).filter(t=>{let r=t.toLowerCase();return r.endsWith(".json")&&!r.includes(".backup")});for(let t of n){let r=l.join(o,t);try{J(r)}catch(s){e.push({dir:l.basename(o),file:t,err:s.message})}}}return e.length===0?{ok:!0}:{ok:!1,broken:e}}function se(e,o){let n=o+".tmp";try{i.copyFileSync(e,n),le(n)}catch(t){try{i.unlinkSync(n)}catch{}throw t}try{i.renameSync(n,o)}catch(t){try{i.unlinkSync(n)}catch{}if(t.code==="EPERM"||t.code==="EBUSY"){let r=new Error(`File locked: ${o} (${t.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=t.code,r}throw t}}function pe(){console.log(`
|
|
31
31
|
\u{1F4E6} Migration status
|
|
32
|
-
`);let e=H();if(console.log(` Data root: ${S}`),console.log(` Schema file: ${m}${i.existsSync(m)?"":" (not yet created \u2014 fresh install)"}`),console.log(` Current version: v${e.version}`),e.migrations?.length){console.log(" Applied migrations:");for(let s of e.migrations){if(!s||typeof s!="object"){console.log(` (malformed entry: ${JSON.stringify(s)})`);continue}let c=String(s.version??"?").padStart(3,"0");console.log(` v${c} \u2014 ${s.name??"(no name)"} (at ${s.appliedAt??"?"})`)}}e.rolledBackAt&&console.log(` Last rollback: ${e.rolledBackAt}`);for(let[s,c]of[["collected-profiles",R],["market-research",T]]){if(!i.existsSync(c))continue;let E=i.readdirSync(c).filter(u=>/\.pre-v\d+\.backup$/.test(u));if(E.length){let u=[...new Set(E.map(p=>p.match(/\.pre-v(\d+)\.backup$/)[1]))].sort();console.log(` Pre-migration backups in ${s}/: ${u.map(p=>`v${p} (${E.filter(f=>f.includes(`pre-v${p}`)).length} files)`).join(", ")}`)}}let o=
|
|
32
|
+
`);let e=H();if(console.log(` Data root: ${S}`),console.log(` Schema file: ${m}${i.existsSync(m)?"":" (not yet created \u2014 fresh install)"}`),console.log(` Current version: v${e.version}`),e.migrations?.length){console.log(" Applied migrations:");for(let s of e.migrations){if(!s||typeof s!="object"){console.log(` (malformed entry: ${JSON.stringify(s)})`);continue}let c=String(s.version??"?").padStart(3,"0");console.log(` v${c} \u2014 ${s.name??"(no name)"} (at ${s.appliedAt??"?"})`)}}e.rolledBackAt&&console.log(` Last rollback: ${e.rolledBackAt}`);for(let[s,c]of[["collected-profiles",R],["market-research",T]]){if(!i.existsSync(c))continue;let E=i.readdirSync(c).filter(u=>/\.pre-v\d+\.backup$/.test(u));if(E.length){let u=[...new Set(E.map(p=>p.match(/\.pre-v(\d+)\.backup$/)[1]))].sort();console.log(` Pre-migration backups in ${s}/: ${u.map(p=>`v${p} (${E.filter(f=>f.includes(`pre-v${p}`)).length} files)`).join(", ")}`)}}let o=ue();o.length>0&&console.log(` Pre-rollback snapshots: ${o.length} file(s) (run 'data-cleanup' to remove)`);let n=ae();if(n.length>0){console.log(` Orphan .tmp files: ${n.length} (will be cleaned on next plugin run, or now via 'data-cleanup')`);for(let s of n)console.log(` ${l.basename(s.dir)}/${s.file}`)}console.log(),console.log(` Latest available schema: v${P}`);let t=O.filter(s=>s.version>e.version);t.length?(console.log(` Pending: ${t.map(s=>`v${String(s.version).padStart(3,"0")}`).join(", ")}`),console.log(" Run any plugin command (e.g. 'ilml linkedin today') to apply pending migrations.")):console.log(" \u2713 Up to date."),console.log();let r=ho();if(r.ok)console.log(" Data integrity: \u2713 all JSON files parse cleanly");else{console.log(` Data integrity: \u2717 ${r.broken.length} file(s) corrupted:`);for(let s of r.broken)console.log(` ${s.dir}/${s.file}: ${s.err}`);console.log(" Migrations will refuse to run until these are repaired.")}console.log()}async function de({confirmed:e=!1}={}){let n=H().version;if(n===0){console.log(`
|
|
33
33
|
No migrations applied \u2014 nothing to roll back.
|
|
34
|
-
`);return}let t=!1,r=`.pre-v${String(n).padStart(3,"0")}.backup`,s=
|
|
34
|
+
`);return}let t=!1,r=`.pre-v${String(n).padStart(3,"0")}.backup`,s=te(R,r),c=te(T,r);if(s.length+c.length===0&&(console.error(`
|
|
35
35
|
[ERROR] No backup files (${r}) found in either data directory.`),console.error(` Cannot roll back v${n} \u2014 backups may have been deleted.
|
|
36
36
|
`),process.exit(1)),!e){console.log(`
|
|
37
37
|
\u26A0 rollback-data ONLY rolls back DATA, NOT the plugin code.
|
|
@@ -41,22 +41,22 @@ var ye=Object.defineProperty;var U=(e,o)=>{for(var n in o)ye(e,n,{get:o[n],enume
|
|
|
41
41
|
`),console.log(" This step 1 will also:"),console.log(" - OVERWRITE current data with the pre-migration backup"),console.log(` (${s.length} files in collected-profiles/, ${c.length} in market-research/)`),console.log(` - Permanently LOSE any data changes made after migration v${n}`),console.log(" (new conversations synced, drafts written, notes added \u2014 all gone)"),console.log(` - Snapshot current state to *.pre-rollback-v${String(n).padStart(3,"0")}.backup before overwriting
|
|
42
42
|
`),console.log(" If you only need to inspect old data without downgrading the plugin,"),console.log(" copy your DATA_DIR somewhere else and use that copy instead \u2014 leaves your"),console.log(` live setup untouched.
|
|
43
43
|
`),console.log(" Proceed with step 1? Re-run with --confirm:"),console.log(" npm run rollback:data -- --confirm"),console.log(` ilml linkedin rollback-data --confirm
|
|
44
|
-
`);return}
|
|
44
|
+
`);return}ce(),ie(),t=!0,process.on("exit",()=>{t&&L()});let u=`.pre-rollback-v${String(n).padStart(3,"0")}.backup`,p=[],f=0;for(let a of[R,T]){if(!i.existsSync(a))continue;let _;try{_=i.readdirSync(a).filter(h=>{let w=h.toLowerCase();if(w.includes(".backup")||w===".schema-version"||w.endsWith(".tmp"))return!1;try{return i.statSync(l.join(a,h)).isFile()}catch{return!1}})}catch(h){p.push(`${l.basename(a)}: ${h.message}`);continue}for(let h of _)try{se(l.join(a,h),l.join(a,h+u)),f++}catch(w){p.push(`${l.basename(a)}/${h}: ${w.message}`)}}if(p.length>0){console.error(`
|
|
45
45
|
[FATAL] ${p.length} file(s) could not be snapshotted:`);for(let a of p)console.error(` ${a}`);console.error(`
|
|
46
46
|
Aborting rollback \u2014 without a complete snapshot of your CURRENT state, a`),console.error(' "change your mind" recovery would silently lose the data you have NOW.'),console.error(` Your current data is unchanged. Pre-rollback snapshots created so far: *${u}`),console.error(" (run 'data-cleanup' to remove them, or retry the rollback once the underlying"),console.error(` issue is resolved \u2014 usually a transient file lock from antivirus or an editor).
|
|
47
|
-
`),process.exit(1)}console.log(` Snapshotted ${f} files \u2192 *${u} (in case you change your mind)`);let b=[],d=[];for(let[a,
|
|
47
|
+
`),process.exit(1)}console.log(` Snapshotted ${f} files \u2192 *${u} (in case you change your mind)`);let b=[],d=[];for(let[a,_]of[[R,s],[T,c]])for(let h of _){let w=l.join(a,h),z=h.slice(0,-r.length),ge=l.join(a,z);try{z.toLowerCase().endsWith(".json")?J(w):i.accessSync(w,i.constants.R_OK),b.push({backupPath:w,originalPath:ge,fileName:h})}catch(ye){d.push({file:h,dir:l.basename(a),err:ye.message})}}if(d.length>0){console.error(`
|
|
48
48
|
[ERROR] ${d.length} backup file(s) are corrupted or unreadable:`);for(let a of d)console.error(` ${a.dir}/${a.file}: ${a.err}`);console.error(`
|
|
49
49
|
Aborting rollback BEFORE touching any original file \u2014`),console.error(" partial restore would leave the DB in an inconsistent state."),console.error(` Your current data is unchanged. Pre-rollback snapshots: *${u}
|
|
50
50
|
`),console.error(" Options:"),console.error(" - Restore the corrupted backup(s) from another source and re-run rollback"),console.error(` - Or accept the new schema and don't roll back
|
|
51
51
|
`),process.exit(1)}b.length===0&&(console.error(`
|
|
52
52
|
[ERROR] No backup files found to restore. Aborting.
|
|
53
|
-
`),process.exit(1));let I=new Date().toISOString(),
|
|
53
|
+
`),process.exit(1));let I=new Date().toISOString(),N=H();N.version=n-1,N.migrations=(N.migrations||[]).filter(a=>a&&Number.isInteger(a.version)&&a.version<n),N.rolledBackAt=I,lo(N);let g=0,v=[];for(let{backupPath:a,originalPath:_,fileName:h}of b)try{se(a,_),g++}catch(w){v.push({fileName:h,err:w.message})}if(v.length>0){console.error(`
|
|
54
54
|
[WARNING] ${v.length} file(s) could not be restored:`);for(let a of v)console.error(` ${a.fileName}: ${a.err}`);console.error(`
|
|
55
55
|
Schema has been lowered to v${n-1} but data is in a mixed state:`),console.error(` ${g} files restored from backup`),console.error(` ${v.length} files still in their post-migration form`),console.error(` The next plugin run will detect pending migration v${n} and re-apply it,`),console.error(" re-converging the data \u2014 but if you intended to roll back, that's not what you want."),console.error(` Manual fix: copy the missing *${r} files back over their originals, then`),console.error(" re-run 'rollback-data --confirm' to redo the rollback. Pre-rollback snapshots"),console.error(` (*${u}) are still on disk if you'd rather restore the v${n} state.
|
|
56
|
-
`),process.exit(1)}try{
|
|
56
|
+
`),process.exit(1)}try{Z()}catch{}try{ee()}catch{}L(),t=!1,console.log(`
|
|
57
57
|
\u2713 STEP 1 DONE. Restored ${g} files. Data schema is now at v${n-1}.
|
|
58
58
|
`),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" \u26A0 STEP 2 REQUIRED \u2014 DO NOT RUN ANY 'ilml linkedin' COMMAND UNTIL"),console.log(` YOU DOWNGRADE THE PLUGIN CODE, or migration v${n} will be re-applied`),console.log(" and this rollback will be silently undone."),console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
59
59
|
`),console.log(" Run NOW (or as soon as possible):"),console.log(` ilml plugin install linkedin@<previous-version>
|
|
60
60
|
`),console.log(` If something looks wrong, your pre-rollback snapshot is in *${u} files`),console.log(` \u2014 copy them back manually to undo this rollback.
|
|
61
|
-
`)}var
|
|
61
|
+
`)}var me=process.argv.slice(2),he=me[0],mo=me.includes("--confirm");async function go(){switch(he){case"status":pe();break;case"rollback-data":await de({confirmed:mo});break;case"data-cleanup":fe();break;default:console.log("Usage:"),console.log(" node migrationCli.mjs status Show schema state + leftover files"),console.log(" node migrationCli.mjs data-cleanup Remove .tmp orphans + pre-rollback snapshots"),console.log(" node migrationCli.mjs rollback-data Preview data rollback (read-only)"),console.log(" node migrationCli.mjs rollback-data --confirm Actually roll back data"),console.log(""),console.log("Note: rollback-data only touches DATA. To roll back the plugin code as well,"),console.log("also run: ilml plugin install linkedin@<previous-version>"),process.exit(he?1:0)}}go().catch(e=>{console.error(`
|
|
62
62
|
[ERROR]`,e.message),process.env.VERBOSE&&console.error(e.stack),process.exit(1)});
|
package/dist/profileHistory.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import"dotenv/config";import*as p from"fs";import*as g from"path";import{getPluginConfig as
|
|
1
|
+
import"dotenv/config";import*as p from"fs";import*as g from"path";import{getPluginConfig as T}from"@ilivemylife/graph-sdk";var x=await T();for(let[e,o]of Object.entries(x))process.env[e]===void 0&&(process.env[e]=o);var Y=process.env.LINKEDIN_NAME,h=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Q=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.NEEDS_REVIEW])),B=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.REJECTED,h.NEEDS_REVIEW]));import"dotenv/config";import*as r from"fs";import*as i from"path";import{fileURLToPath as _}from"url";var R=_(import.meta.url),k=i.dirname(i.dirname(R)),u=process.env.DATA_DIR?i.resolve(process.env.DATA_DIR,"collected-profiles"):i.join(k,"collected-profiles"),v=i.join(u,"people.json"),Z=i.join(u,"jobs.json"),ee=i.join(u,"quota.json"),ne=i.join(u,"conversations.json"),oe=i.join(u,"sync-state.json"),te=i.join(u,"application-questions.json"),N=i.join(u,"profile-history"),E=(e,o)=>{let t=parseInt(e,10);return Number.isInteger(t)&&t>=0?t:o},se=E(process.env.CONNECT_MAX_DAILY,20),le=E(process.env.CONNECT_MAX_WEEKLY,100),j=null;function L(){r.existsSync(u)||r.mkdirSync(u,{recursive:!0})}function $(e,o){if(!r.existsSync(e))return null;try{let t=JSON.parse(r.readFileSync(e,"utf-8"));try{r.copyFileSync(e,e+".backup")}catch{}return t}catch(t){console.error(`[!] ${o} is corrupted: ${t.message}`),console.error(` File: ${i.resolve(e)}`);let s=e+".backup";if(r.existsSync(s))try{let n=JSON.parse(r.readFileSync(s,"utf-8"));console.error(` Restored from ${s}`);try{r.copyFileSync(s,e)}catch{}return n}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{r.copyFileSync(e,e+".corrupted")}catch{}return null}}function m(e){if(!e)return null;let o=e.match(/\/in\/([^/?#]+)/);return o?o[1].toLowerCase():null}function M(e){let o=m(e);return o?i.join(N,`${o}.json`):null}function I(e){let o=M(e);return o?$(o,`profile-history/${m(e)}.json`)||[]:[]}function S(){return j||(L(),j=$(v,"people.json")||{},j)}var b=process.argv.slice(2),J=b.includes("--all"),D=b.find(e=>e.startsWith("--since="))?.split("=")[1]||null,d=b.find(e=>e.startsWith("--field="))?.split("=")[1]||null,O=b.find(e=>!e.startsWith("--"))||null,P=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(process.cwd(),"collected-profiles"),y=g.join(P,"profile-history");function F(){return p.existsSync(y)?p.readdirSync(y).filter(e=>e.endsWith(".json")).map(e=>e.replace(/\.json$/,"")):[]}function V(e){let o=S();for(let t of Object.keys(o))if(m(t)===e)return t;return null}function W(e){let o=e.startsWith("http")?m(e):e.toLowerCase();if(!o){console.log("Could not derive slug");return}let t=V(o),s=S(),n=t?s[t]:null,a=t?I(t):(()=>{let l=g.join(y,`${o}.json`);return p.existsSync(l)?JSON.parse(p.readFileSync(l,"utf-8")):[]})();console.log(`
|
|
2
2
|
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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(` ${n?.name||"(unknown)"} \u2014 ${o}`),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
|
|
3
3
|
`),n&&(console.log("CURRENT:"),console.log(` title: ${n.title||"(null)"}`),console.log(` location: ${n.location||"(null)"}`),console.log(` currentCompany: ${n.currentCompany||"(null)"}`),console.log(` connectionDeg: ${n.connectionDegree||"(null)"}`),console.log(` openToWork: ${n.openToWork}`),console.log(` hiring: ${n.hiring}`),console.log(` lastUpdated: ${n.lastUpdated||"(null)"}`),console.log(` lastSnapshotAt: ${n.lastSnapshotAt||"(null)"}`)),console.log(`
|
|
4
4
|
HISTORY (${a.length} snapshot${a.length===1?"":"s"}):
|
|
5
|
-
`);for(let l of a){if(console.log(` ${l.capturedAt} changed: [${(l.changedFields||[]).join(", ")}]`),l.snapshot)for(let c of l.changedFields||[]){let f=l.snapshot[c],
|
|
5
|
+
`);for(let l of a){if(console.log(` ${l.capturedAt} changed: [${(l.changedFields||[]).join(", ")}]`),l.snapshot)for(let c of l.changedFields||[]){let f=l.snapshot[c],C=n?n[c]:"?",w=A=>Array.isArray(A)?A.join(", "):A??"(null)";console.log(` ${c.padEnd(18)} OLD: ${w(f)}`),console.log(` ${" ".repeat(18)} NEW: ${w(C)}`)}console.log()}}function U(){let e=F();console.log(`
|
|
6
6
|
${e.length} profiles have history files:
|
|
7
|
-
`);let o=S(),
|
|
8
|
-
${
|
|
9
|
-
`);for(let n of
|
|
7
|
+
`);let o=S(),t=[];for(let s of e){let n=Object.keys(o).find(f=>m(f)===s),a=n?o[n]:null,l=g.join(y,`${s}.json`),c=JSON.parse(p.readFileSync(l,"utf-8"));t.push({slug:s,name:a?.name||"(unknown)",snapshots:c.length,mostRecentChange:c[0]?.capturedAt||null,mostRecentFields:c[0]?.changedFields?.join(", ")||""})}t.sort((s,n)=>(n.mostRecentChange||"").localeCompare(s.mostRecentChange||""));for(let s of t)console.log(` ${s.mostRecentChange?.slice(0,10)||"----------"} ${s.snapshots}\xD7 ${s.name.padEnd(28)} [${s.mostRecentFields}]`)}function q(){let e=F(),o=new Date(D).getTime(),t=S(),s=[];for(let n of e){let a=g.join(y,`${n}.json`),l=JSON.parse(p.readFileSync(a,"utf-8"));for(let c of l){if(new Date(c.capturedAt).getTime()<o||d&&!(c.changedFields||[]).includes(d))continue;let f=Object.keys(t).find(C=>m(C)===n);s.push({date:c.capturedAt,slug:n,name:(f?t[f]:null)?.name||"(unknown)",fields:(c.changedFields||[]).join(", "),...d?{oldValue:c.snapshot?.[d],newValue:(f?t[f]:null)?.[d]}:{}})}}s.sort((n,a)=>a.date.localeCompare(n.date)),console.log(`
|
|
8
|
+
${s.length} change events since ${D}${d?` (field=${d})`:""}:
|
|
9
|
+
`);for(let n of s)if(console.log(` ${n.date.slice(0,10)} ${n.name.padEnd(28)} [${n.fields}]`),d){let a=l=>Array.isArray(l)?l.join(", "):l??"(null)";console.log(` OLD: ${a(n.oldValue)}`),console.log(` NEW: ${a(n.newValue)}`)}}O?W(O):J?U():D?q():(console.log("Usage:"),console.log(" node profileHistory.mjs <slug-or-url>"),console.log(" node profileHistory.mjs --all"),console.log(" node profileHistory.mjs --since=YYYY-MM-DD [--field=NAME]"));
|
package/dist/quota.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{getPluginConfig as L}from"@ilivemylife/graph-sdk";var
|
|
1
|
+
import{getPluginConfig as L}from"@ilivemylife/graph-sdk";var M=await L();for(let[e,n]of Object.entries(M))process.env[e]===void 0&&(process.env[e]=n);var Z=process.env.LINKEDIN_NAME,u=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),ee=Object.freeze(new Set([u.DRAFT,u.CONFIRMED,u.NEEDS_REVIEW])),ne=Object.freeze(new Set([u.DRAFT,u.CONFIRMED,u.REJECTED,u.NEEDS_REVIEW]));import"dotenv/config";import*as o from"fs";import*as s from"path";import{fileURLToPath as P}from"url";var q=P(import.meta.url),J=s.dirname(s.dirname(q)),l=process.env.DATA_DIR?s.resolve(process.env.DATA_DIR,"collected-profiles"):s.join(J,"collected-profiles"),U=s.join(l,"people.json"),ie=s.join(l,"jobs.json"),V=s.join(l,"quota.json"),ce=s.join(l,"conversations.json"),le=s.join(l,"sync-state.json"),ae=s.join(l,"application-questions.json"),ue=s.join(l,"profile-history"),E=(e,n)=>{let t=parseInt(e,10);return Number.isInteger(t)&&t>=0?t:n},w=E(process.env.CONNECT_MAX_DAILY,20),A=E(process.env.CONNECT_MAX_WEEKLY,100),g=null;var p=null;function C(){o.existsSync(l)||o.mkdirSync(l,{recursive:!0})}function D(e,n){if(!o.existsSync(e))return null;try{let t=JSON.parse(o.readFileSync(e,"utf-8"));try{o.copyFileSync(e,e+".backup")}catch{}return t}catch(t){console.error(`[!] ${n} is corrupted: ${t.message}`),console.error(` File: ${s.resolve(e)}`);let r=e+".backup";if(o.existsSync(r))try{let c=JSON.parse(o.readFileSync(r,"utf-8"));console.error(` Restored from ${r}`);try{o.copyFileSync(r,e)}catch{}return c}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{o.copyFileSync(e,e+".corrupted")}catch{}return null}}function Q(){return new Date().toISOString().split("T")[0]}function m(e){if(!e)return null;let n=e.match(/\/in\/([^/?#]+)/);return n?n[1].toLowerCase():null}function O(){return g||(C(),g=D(U,"people.json")||{},g)}function h(){return p||(C(),p=D(V,"quota.json"),(!p||!p.connections)&&(p={connections:[]}),p)}function W(){let e=h(),n=new Date;n.setDate(n.getDate()-7);let t=n.toISOString().split("T")[0];return e.connections.filter(r=>r.date>=t)}function I(){let e=W(),n=Q(),t=e.filter(c=>c.date===n).length,r=e.length;return{daily:{sent:t,max:w,remaining:w-t},weekly:{sent:r,max:A,remaining:A-r}}}var B=process.argv.includes("--json"),k=new Set(["connected"]),$=new Set(["pending","sent_without_note","sent_with_note","sent_without_note_upsell"]),j=["founder","investor","hiring_manager","peer"],Y=new Date().toISOString().split("T")[0];function F(e){if(!e)return 1/0;let n=new Date(e+"T00:00:00Z").getTime(),t=new Date(Y+"T00:00:00Z").getTime();return Math.round((t-n)/864e5)}function H(e,n,t=20){return n?"\u2588".repeat(Math.max(0,Math.round(e/n*t))):""}var z=h(),y=z.connections||[],i=I(),v=O(),R=new Map;for(let[e,n]of Object.entries(v)){let t=m(n.profileUrl||e);t&&R.set(t,n)}var K=y.filter(e=>F(e.date)<=7),a={},S={};for(let e of K){a[e.date]=(a[e.date]||0)+1;let n=e.source||"unknown";S[n]=(S[n]||0)+1}var f={},T=new Set;for(let e of y){let n=e.source||"unknown",t=m(e.profileUrl),r=`${n}|${t}`;if(t&&T.has(r))continue;t&&T.add(r),f[n]||={sent:0,accepted:0,awaiting:0,stale:0,unknown:0};let c=f[n];c.sent++;let b=(t?R.get(t):null)?.connectionStatus;k.has(b)?c.accepted++:$.has(b)?(c.awaiting++,F(e.date)>14&&c.stale++):c.unknown++}var d={};for(let e of j)d[e]=0;for(let e of Object.values(v)){let n=e.category;if(!j.includes(n))continue;let t=e.connectionStatus;k.has(t)||$.has(t)||e.connectionDegree!=="1st"&&d[n]++}var N=Object.values(d).reduce((e,n)=>e+n,0);B&&(console.log(JSON.stringify({state:i,byDay:a,bySource7d:S,acceptance:f,feederPool:{...d,total:N},totalLogged:y.length},null,2)),process.exit(0));var x=(e,n)=>n?`${Math.round(e/n*100)}%`:"\u2014";console.log(`
|
|
2
2
|
=== LinkedIn Connection Quota ===
|
|
3
3
|
`);console.log("State");console.log(` Today: ${i.daily.sent} / ${i.daily.max} (${i.daily.remaining} left)`);console.log(` Week: ${i.weekly.sent} / ${i.weekly.max} (${i.weekly.remaining} left) \u2014 rolling 7 days`);console.log(` Log: ${y.length} requests recorded all-time
|
|
4
|
-
`);if(Object.keys(a).length){let e=Math.max(...Object.values(a));console.log("Last 7 days");for(let
|
|
4
|
+
`);if(Object.keys(a).length){let e=Math.max(...Object.values(a));console.log("Last 7 days");for(let n of Object.keys(a).sort())console.log(` ${n} ${H(a[n],e).padEnd(20)} ${a[n]}`);console.log(""),console.log(" by source (7d): "+Object.entries(S).map(([n,t])=>`${n}=${t}`).join(", ")+`
|
|
5
5
|
`)}else console.log(`Last 7 days: no connection requests sent.
|
|
6
|
-
`);var
|
|
6
|
+
`);var _=Object.keys(f).sort((e,n)=>f[n].sent-f[e].sent);if(_.length){console.log("Acceptance (all-time, unique profiles)"),console.log(" "+"Source".padEnd(24)+"Sent".padStart(6)+"Accept".padStart(8)+"Rate".padStart(7)+"Await".padStart(7)+"Stale>14d".padStart(11));let e={sent:0,accepted:0,awaiting:0,stale:0};for(let n of _){let t=f[n];e.sent+=t.sent,e.accepted+=t.accepted,e.awaiting+=t.awaiting,e.stale+=t.stale,console.log(" "+n.padEnd(24)+String(t.sent).padStart(6)+String(t.accepted).padStart(8)+x(t.accepted,t.sent).padStart(7)+String(t.awaiting).padStart(7)+String(t.stale).padStart(11))}console.log(" "+"\u2500".repeat(63)),console.log(" "+"TOTAL".padEnd(24)+String(e.sent).padStart(6)+String(e.accepted).padStart(8)+x(e.accepted,e.sent).padStart(7)+String(e.awaiting).padStart(7)+String(e.stale).padStart(11)),console.log(" (Accept = they connected \xB7 Await = request still open \xB7 Stale>14d = open >2wk, likely won't)"),console.log(" Acceptance is read from your last `sync` \u2014 run `ilml linkedin sync` to refresh accepted status.\n")}console.log("Feeder pool \u2014 high-value people not yet connected");for(let e of j)console.log(` ${e.padEnd(16)} ${d[e]}`);console.log(` ${"TOTAL".padEnd(16)} ${N}`);console.log(` \u2192 these could absorb idle daily quota once the category feeder is wired.
|
|
7
7
|
`);i.daily.sent===0&&i.daily.remaining>0&&(console.log(`\u26A0 Today's quota is idle (0/${i.daily.max}). Recruiter auto-connect is off and no`),console.log(` other feeder is wired yet \u2014 run the funnel manually or build the category feeder.
|
|
8
8
|
`));
|