ilml-plugin-linkedin 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.1
4
+ - **`ilml linkedin warm-scan` now actually works** — `warmScan.mjs` was previously only reachable through `npm run warm-scan` in the dev repo. Wired through to the published plugin: added `warm-scan` to `ilml-plugin.json` commands, `warmScan.mjs` to the build entry points, and `target-companies.json` to the npm tarball. The script now finds `target-companies.json` in three locations, in order: `<DATA_DIR>/target-companies.json` (user override), `<package-root>/target-companies.json` (shipped default for prod), source dir (dev mode)
5
+ - README polish — leads with the killer differentiators that were buried before: AI drafts that go through user review with re-check on push, auto-triaged inbox classification, `--skip-unread` sync that preserves LinkedIn unread badges. Daily-commands table descriptions tightened (especially `today`, `enrich`, `daily`, which were vague or wrong); added `apply-queue` and `warm-scan` rows; new "Common routines" section with morning-triage / active-search / passive-pipeline / targeted-networking patterns
6
+ - `package.json` — new npm `description` (one line that says what the plugin does, not just a list of subcommands), expanded `keywords` for npm discovery (`linkedin-bot`, `linkedin-automation`, `easy-apply`, `job-search`, `inbox-sync`, `recruiter-outreach`, `ai-messaging`)
7
+
3
8
  ## 1.3.0
4
9
  - **Behavior change — LinkedIn session cookies now live in the active ilml scope** instead of in the plugin install directory. New path: `<scope>/.ilivemylife/plugins-state/linkedin/cookies.json` (production) / `./cookies.json` next to `ilml-plugin.json` (dev mode, unchanged). This means:
5
10
  - `ilml plugin update linkedin` no longer wipes your LinkedIn session — sessions survive plugin updates
package/README.md CHANGED
@@ -11,7 +11,9 @@ It also automates LinkedIn from the terminal — applying to jobs, drafting outr
11
11
  ## What you get
12
12
 
13
13
  - **A full local mirror of your LinkedIn graph.** Connections, conversation threads, message history, profile metadata, your private notes about people — synced into your iLiveMyLife graph and a local database. Nothing stays trapped in linkedin.com.
14
- - **AI-assisted messaging.** [Lifebot](https://ilivemylife.io) reads the full context of each thread (history, your notes, tags) and helps you draft replies in your voice. No copy-pasting into ChatGPT, no losing context, no generic templates.
14
+ - **AI-assisted messaging — drafts go through you.** [Lifebot](https://ilivemylife.io) reads the full context of each thread (history, your notes, tags) and drafts replies in your voice. You review the batch in one pass and push approved drafts as a group. On push, the bot re-scans every thread first — if a new incoming message arrived since you reviewed, it pauses that draft and flags it for re-review instead of sending blind. Never autopilot.
15
+ - **An auto-triaged inbox.** Every conversation gets classified (recruiter / hiring manager / founder / investor / spam / event), tagged, prioritized, and stamped with a suggested next action. `ilml linkedin today` prints a no-browser daily plan: who to reply to, what to do, in priority order — before you've even opened LinkedIn.
16
+ - **Inbox sync that doesn't break your workflow.** A `--skip-unread` mode pulls the conversation list and metadata without "opening" unread threads — LinkedIn keeps showing the unread badges until *you* read them in the UI. Use the bot as an analyst without losing your own attention markers.
15
17
  - **Terminal-first automation.** Batch operations — apply to dozens of jobs at once, draft 50 outreach messages then review them all in one pass, run a connection campaign on a schedule. Scriptable. Re-runnable. Logged.
16
18
  - **Easy Apply that actually thinks.** Auto-fills LinkedIn job applications, with Lifebot answering custom questions ("Why are you interested in this role?") in your voice using context from your graph — not boilerplate.
17
19
  - **Your data outlives the plugin.** The connection database, conversation history, and your notes live in a directory **you** choose (`DATA_DIR`). Plugin updates don't wipe them. Logging out of ilml doesn't wipe them. Uninstalling the plugin doesn't wipe them. Only **you** decide when they go.
@@ -158,21 +160,56 @@ Sessions live in your active ilml scope (`<scope>/.ilivemylife/plugins-state/lin
158
160
 
159
161
  | Command | What it does |
160
162
  |---|---|
161
- | `ilml linkedin apply` | Auto-apply to LinkedIn Easy Apply jobs. Stops after `MAX_APPLY_FOR_RUN`. |
162
- | `ilml linkedin sync` / `sync-all` | Pull the LinkedIn inbox into the local DB and the graph. |
163
- | `ilml linkedin today` | Faster sync of just today's activity. |
164
- | `ilml linkedin enrich` | Categorize and tag conversations. |
165
- | `ilml linkedin report` | Summary of the latest session. |
166
- | `ilml linkedin scout` | Scan jobs against `LINKEDIN_SCOUT_URLS` without applying. |
167
- | `ilml linkedin messages` | Batch messaging draft, review, then push. Nothing sends without your approval. |
168
- | `ilml linkedin funnel` | Process a queue of recruiter / founder / investor profiles. |
169
- | `ilml linkedin visit` | Track profile visits. |
170
- | `ilml linkedin viewers` | List who viewed your profile. |
171
- | `ilml linkedin daily` | Compound routine combining several commands. |
163
+ | `ilml linkedin apply` | Auto-apply to LinkedIn Easy Apply jobs from `LINKEDIN_SEARCH_URL`. Stops after `MAX_APPLY_FOR_RUN`. |
164
+ | `ilml linkedin sync-all` | Pull the LinkedIn inbox into the local DB and the graph. Add `--skip-unread` to scan without opening unread threads (preserves LinkedIn's unread badges). `--full` for a full re-scan, `--report` for stats only with no browser. |
165
+ | `ilml linkedin today` | No-browser daily plan: who to reply to, what to do, in priority order. Reads already-synced data — doesn't open LinkedIn. |
166
+ | `ilml linkedin enrich` | Re-classify every conversation (recruiter / hiring manager / founder / investor / spam / event) and rebuild priorities, summaries, and suggested actions. Offline, no browser. |
167
+ | `ilml linkedin report` | Print the summary of the latest session. |
168
+ | `ilml linkedin scout` | Scan jobs from `LINKEDIN_SCOUT_URLS` and score them — without applying. Builds a queue for `apply-queue`. |
169
+ | `ilml linkedin apply-queue` | Apply to the top-scored jobs from the latest `scout` run. |
170
+ | `ilml linkedin messages` | Batch messaging draft, review, then push. Nothing sends without your approval. Common forms: `messages --review-drafts` (review pending drafts) and `messages --push-drafts` (push approved). |
171
+ | `ilml linkedin funnel` | Run the recruiter / founder / investor connection-request queue. |
172
+ | `ilml linkedin visit` | Visit recruiter / target profiles so they see you in "who viewed your profile". |
173
+ | `ilml linkedin viewers` | Pull the "who viewed your profile" list. |
174
+ | `ilml linkedin warm-scan` | Find 1st-degree connections at target companies (offline, no browser). Reads your already-synced people / conversations and groups them by company so you know who you can message directly versus where you need cold outreach. Override the company list via `--companies="Anthropic,OpenAI"` or pick a named group with `--group=topPaying`. |
175
+ | `ilml linkedin daily` | Full pipeline in one command: `sync-all` → `apply` → `scout` → `funnel` → `visit` → `viewers` → run report. |
172
176
  | `ilml linkedin login` | Re-save LinkedIn cookies after a session expires. |
173
177
 
174
178
  Run `ilml linkedin` (no subcommand) any time to see the list.
175
179
 
180
+ ## Common routines
181
+
182
+ A few patterns that string the daily commands together. Outgoing messages always require your explicit approval (via `messages --push-drafts`); commands like `apply` and `funnel` do submit Easy Apply forms and connection requests on their own, scoped by `MAX_APPLY_FOR_RUN` and `FUNNEL_MAX_CONNECTIONS`.
183
+
184
+ **Morning triage** — see what needs attention before opening LinkedIn:
185
+ ```bash
186
+ ilml linkedin sync-all --skip-unread # pull new messages without losing unread badges
187
+ ilml linkedin today # print prioritized day plan
188
+ ```
189
+
190
+ **Active job search** — apply, scout, network on autopilot; you stay in the loop on outgoing messages:
191
+ ```bash
192
+ ilml linkedin daily # apply + scout + funnel + visit + viewers + report
193
+ ilml linkedin messages --review-drafts # later: review the AI's drafted replies
194
+ ilml linkedin messages --push-drafts # push the ones you approved
195
+ ```
196
+
197
+ **Passive pipeline** — keep the inbox synced and triaged without touching outgoing channels:
198
+ ```bash
199
+ ilml linkedin sync-all --skip-unread
200
+ ilml linkedin enrich
201
+ ilml linkedin today
202
+ ```
203
+
204
+ **Targeted networking** — figure out which target companies you can reach via existing connections before sending any cold outreach:
205
+ ```bash
206
+ ilml linkedin warm-scan # default group
207
+ ilml linkedin warm-scan --group=topPaying # broader (FAANG + tier-1 + high-comp)
208
+ ilml linkedin warm-scan --companies="Anthropic,OpenAI" # one-off override
209
+ ```
210
+
211
+ Compose your own from the commands above — each one is a standalone script.
212
+
176
213
  ## Updating
177
214
 
178
215
  Updates ship via npm. The CLI checks the registry and only downloads when a newer version is actually available:
@@ -0,0 +1,5 @@
1
+ import"dotenv/config";import*as l from"fs";import*as s from"path";import{fileURLToPath as A}from"url";var g=s.dirname(A(import.meta.url)),y=process.env.DATA_DIR?s.resolve(process.env.DATA_DIR,"collected-profiles"):s.join(g,"collected-profiles"),a={};for(let n of process.argv.slice(2)){let e=n.match(/^--([^=]+)(?:=(.*))?$/);e&&(a[e[1]]=e[2]??!0)}var h=a.group||"apiLeaders",d=a["min-seniority"]||null,v=!!a["show-all"],S=[process.env.DATA_DIR&&s.resolve(process.env.DATA_DIR,"target-companies.json"),s.join(g,"target-companies.json"),s.resolve(g,"..","target-companies.json")].filter(Boolean),C=S.find(n=>l.existsSync(n));C||(console.error(`target-companies.json not found. Tried:
2
+ `+S.join(`
3
+ `)),console.error("Set DATA_DIR and place your own target-companies.json there, or reinstall the plugin to restore the shipped default."),process.exit(1));var j=JSON.parse(l.readFileSync(C,"utf8")),u=a.companies?String(a.companies).split(",").map(n=>n.trim()).filter(Boolean):j[h]?.companies;u||(console.error(`Unknown group: ${h}. Available: ${Object.keys(j).join(", ")}`),process.exit(1));var R=JSON.parse(l.readFileSync(s.join(y,"people.json"),"utf8")),b=JSON.parse(l.readFileSync(s.join(y,"conversations.json"),"utf8")),m=new Map;for(let[n,e]of Object.entries(b))typeof e.isConnection=="boolean"&&m.set(n,{isConnection:e.isConnection,conv:e});function k(n){if(n.name&&m.has(n.name)){let e=m.get(n.name);return{is:e.isConnection,src:"A.convInbox",conv:e.conv}}return n.connectionDegree==="1st"?{is:!0,src:"B.profileScrape"}:n.connectionDegree==="2nd"||n.connectionDegree==="3rd"?{is:!1,src:"B.profileScrape"}:n.connectionStatus==="connected"?{is:!0,src:"C.botStatus"}:["sent_without_note","sent_with_note","withdrawn","rejected"].includes(n.connectionStatus)?{is:!1,src:"C.botStatus"}:{is:null,src:null}}var $={intern:0,junior:1,mid:2,senior:3,lead:4,staff:4,principal:5,director:5,vp:6};function O(n,e){if(!e)return!0;if(!n)return!1;let r=n.toLowerCase(),i=$[e.toLowerCase()];if(i==null)return!0;for(let[o,t]of Object.entries($))if(r.includes(o)&&t>=i)return!0;return!1}function T(n,e){return[n.company,n.title,e?.personContext?.title,e?.personContext?.company].filter(Boolean).join(" | ").toLowerCase()}function x(n){let e=n.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${e}\\b`,"i")}var B=u.map(n=>({name:n,re:x(n)})),p={};for(let n of u)p[n]={connected:[],notConnected:[],unknown:[]};for(let[n,e]of Object.entries(R)){let r=e.name?b[e.name]:null,i=T(e,r);if(i)for(let{name:o,re:t}of B){if(!t.test(i))continue;if(!O(e.title||r?.personContext?.title,d))break;let c=k(e),f={name:e.name||n.slice(-40),title:e.title||r?.personContext?.title||"",company:e.company,degree:e.connectionDegree,via:c.src,convStatus:c.conv?.conversationStatus,lastMsg:c.conv?.lastMessageTime,lastBy:c.conv?.lastMessageBy,category:c.conv?.category||e.category};c.is===!0?p[o].connected.push(f):c.is===!1?p[o].notConnected.push(f):p[o].unknown.push(f);break}}var _=d?` (min-seniority=${d})`:"";console.log(`
4
+ \u2550\u2550 Warm-scan: ${h}${_} \u2014 ${u.length} target companies \u2550\u2550
5
+ `);var I=Object.entries(p).sort((n,e)=>e[1].connected.length-n[1].connected.length),w=0;for(let[n,e]of I){let r=e.connected.length+e.notConnected.length+e.unknown.length;w+=e.connected.length;let i=`${n.padEnd(18)} connected: ${e.connected.length}, other: ${e.notConnected.length+e.unknown.length}, total seen: ${r}`;if(console.log(i),e.connected.length>0)for(let t of e.connected){let c=(t.title||"").slice(0,55),f=t.convStatus?` [${t.convStatus}]`:"",D=t.lastMsg?` (${t.lastMsg.slice(0,10)})`:"";console.log(` \xB7 ${t.name.padEnd(28)} ${c}${f}${D}`)}let o=[...e.notConnected,...e.unknown];if(o.length>0&&(v||o.length<=5)){console.log(" seen but not 1st-degree:");for(let t of o.slice(0,v?o.length:5))console.log(` ${t.name.padEnd(28)} ${(t.title||"").slice(0,55)}`)}else o.length>5&&console.log(` seen but not 1st-degree: ${o.length} \u2014 add --show-all or --companies="${n}" to expand`);console.log("")}console.log(`Total 1st-degree at target companies: ${w}`);
package/ilml-plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "LinkedIn job automation: apply, scout, sync, funnel, messages",
5
5
  "commands": {
6
6
  "apply": { "run": "node dist/run.mjs" },
@@ -15,6 +15,7 @@
15
15
  "visit": { "run": "node dist/visit.mjs" },
16
16
  "viewers": { "run": "node dist/viewers.mjs" },
17
17
  "apply-queue": { "run": "node dist/applyQueue.mjs" },
18
+ "warm-scan": { "run": "node dist/warmScan.mjs" },
18
19
  "daily": { "run": "node dist/daily.mjs" },
19
20
  "login": { "run": "node dist/src/saveLoginState.mjs" }
20
21
  },
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "ilml-plugin-linkedin",
3
- "version": "1.3.0",
4
- "description": "ilml plugin: LinkedIn job automation apply, scout, sync, funnel, messages",
3
+ "version": "1.3.1",
4
+ "description": "ilml plugin: pull your LinkedIn graph (connections, threads, notes) into iLiveMyLife and run inbox sync, AI-drafted replies, Easy Apply, and recruiter outreach from the terminal.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
8
8
  "ilml-plugin.json",
9
+ "target-companies.json",
9
10
  "CHANGELOG.md"
10
11
  ],
11
12
  "scripts": {
@@ -62,7 +63,13 @@
62
63
  "ilml-plugin",
63
64
  "ilivemylife",
64
65
  "linkedin",
65
- "automation",
66
+ "linkedin-bot",
67
+ "linkedin-automation",
68
+ "easy-apply",
69
+ "job-search",
70
+ "inbox-sync",
71
+ "recruiter-outreach",
72
+ "ai-messaging",
66
73
  "puppeteer"
67
74
  ],
68
75
  "author": "Ilya Sorokin <info@ilivemylife.io> (https://ilivemylife.io)",
@@ -72,7 +79,7 @@
72
79
  },
73
80
  "homepage": "https://github.com/iLiveMyLife/linkedInJobsAutoApply#readme",
74
81
  "dependencies": {
75
- "@ilivemylife/graph-sdk": "^1.0.17",
82
+ "@ilivemylife/graph-sdk": "^1.0.18",
76
83
  "dotenv": "^16.4.5",
77
84
  "puppeteer": "^22.15.0",
78
85
  "puppeteer-extra": "^3.3.6",
@@ -0,0 +1,58 @@
1
+ {
2
+ "apiLeaders": {
3
+ "description": "API/AI industry leaders (original focus). Substring match, word-boundary.",
4
+ "companies": [
5
+ "Anthropic",
6
+ "OpenAI",
7
+ "Stripe",
8
+ "Plaid",
9
+ "Twilio",
10
+ "Mastercard",
11
+ "Visa",
12
+ "Square",
13
+ "Block",
14
+ "Ramp",
15
+ "Retool",
16
+ "Linear",
17
+ "Cohere",
18
+ "Hugging Face",
19
+ "Scale AI"
20
+ ]
21
+ },
22
+ "topPaying": {
23
+ "description": "FAANG + tier-1 tech + top AI + high-comp fintech/SaaS — senior engineer total comp typically $300K+. Broader target when user isn't picky about domain, only about pay.",
24
+ "companies": [
25
+ "Meta",
26
+ "Google",
27
+ "Amazon",
28
+ "Apple",
29
+ "Netflix",
30
+ "Microsoft",
31
+ "Uber",
32
+ "Airbnb",
33
+ "LinkedIn",
34
+ "Pinterest",
35
+ "Anthropic",
36
+ "OpenAI",
37
+ "Cohere",
38
+ "Databricks",
39
+ "Snowflake",
40
+ "Hugging Face",
41
+ "Scale AI",
42
+ "Stripe",
43
+ "Plaid",
44
+ "Mastercard",
45
+ "Visa",
46
+ "Square",
47
+ "Block",
48
+ "Ramp",
49
+ "Datadog",
50
+ "MongoDB",
51
+ "Figma",
52
+ "Notion",
53
+ "HashiCorp",
54
+ "Retool",
55
+ "Linear"
56
+ ]
57
+ }
58
+ }