ilml-plugin-linkedin 1.2.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +302 -369
  2. package/ilml-plugin.json +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,369 +1,302 @@
1
- # LinkedIn Automation Toolkit
2
-
3
- Автоматизация LinkedIn: подача заявок на вакансии, мониторинг входящих сообщений, управление контактами и рассылка сообщений.
4
-
5
- ---
6
-
7
- ## Быстрый старт (5 минут)
8
-
9
- ### Шаг 1. Установи Node.js
10
-
11
- Зайди на https://nodejs.org/ и нажми зелёную кнопку **LTS**. Запусти скачанный файл и нажимай "Next" до конца.
12
-
13
- Проверь что установилось открой терминал и набери:
14
- ```
15
- node --version
16
- ```
17
- Должно показать версию (например `v22.x.x`).
18
-
19
- > **Как открыть терминал:**
20
- > - **Windows**: нажми `Win+R`, набери `cmd`, нажми Enter
21
- > - **Mac**: открой приложение Terminal (Cmd+Space → набери Terminal)
22
-
23
- ### Шаг 2. Распакуй проект
24
-
25
- Распакуй `linkedin-auto-apply.zip` в любую папку (например на рабочий стол).
26
-
27
- ### Шаг 3. Открой терминал в папке проекта
28
-
29
- - **Windows**: открой папку в проводнике → кликни на адресную строку вверху → набери `cmd` → Enter
30
- - **Mac**: Terminal набери `cd ` (с пробелом) перетащи папку в окно → Enter
31
-
32
- ### Шаг 4. Установи зависимости
33
-
34
- ```
35
- npm install
36
- ```
37
- Подожди 1-2 минуты (скачается ~200 MB для встроенного браузера).
38
-
39
- ### Шаг 5. Настрой конфигурацию
40
-
41
- Скопируй шаблон настроек:
42
- ```
43
- copy .env.example .env
44
- ```
45
- > На Mac/Linux: `cp .env.example .env`
46
-
47
- Открой файл `.env` в **Блокноте** (Windows) или **TextEdit** (Mac) и заполни:
48
-
49
- ```ini
50
- # === Обязательные ===
51
- LINKEDIN_NAME=Иван Петров # Твоё имя ТОЧНО как на LinkedIn
52
- LINKEDIN_LOGIN=ivan@gmail.com # ← Email для входа в LinkedIn
53
- LINKEDIN_PASS=mypassword123 # Пароль от LinkedIn
54
-
55
- # === iLiveMyLife (нужно для подачи заявок) ===
56
- NODE_EASY_APPLY_MAIN=id_узла # ID основного узла (см. Шаг 5б)
57
- NODE_EASY_APPLY_ERRORS=id_узла # ← ID узла для ошибок (см. Шаг 5б)
58
- ```
59
-
60
- > Токен iLiveMyLife не нужно прописывать в `.env` — он подхватывается автоматически после `ilml login` (Шаг 5б).
61
-
62
- > **LINKEDIN_NAME** самая важная настройка. Открой свой LinkedIn профиль и скопируй имя точно как оно показано (включая язык и регистр).
63
-
64
- ### Шаг 5б. Настрой iLiveMyLife (нужно для `npm run apply`)
65
-
66
- Бот использует AI-ассистента **Lifebot** для принятия решений: подавать ли на вакансию, как заполнять анкеты. Без этого шага `npm run apply` не запустится.
67
-
68
- > **Мониторинг сообщений** (`npm run sync-all`, `npm run today`) работает без iLiveMyLife. Этот шаг нужен только для автоматической подачи заявок.
69
-
70
- #### 1. Создай аккаунт
71
-
72
- Зайди на https://app.ilivemylife.io и зарегистрируйся (бесплатно).
73
-
74
- #### 2. Установи iLiveMyLife CLI и залогинься
75
-
76
- ```
77
- npm install -g @ilivemylife/graph-sdk
78
- ilml login
79
- ```
80
-
81
- > Подробнее о CLI: https://www.npmjs.com/package/@ilivemylife/graph-sdk
82
-
83
- CLI спросит email и пароль от iLiveMyLife. После успешного входа токен сохранится автоматически в `~/.ilivemylife/config.json` — бот подхватит его оттуда, ничего в `.env` прописывать не нужно.
84
-
85
- > **Бонус:** `ilml` CLI — это ещё и инструмент для работы с графом из терминала. Попробуй `ilml doctor` чтобы проверить что всё настроено, или `ilml help` для списка команд.
86
-
87
- #### 3. Создай главный узел для подачи заявок
88
-
89
- 1. На главной странице нажми **+** (создать узел)
90
- 2. Назови его **"LinkedIn Easy Apply"**
91
- 3. Открой узел в правом меню включи переключатель **AI-ассистент** (Lifebot)
92
- > Это включает AI для этого узла. **Без этого бот не сможет принимать решения!**
93
- 4. Скопируй **ID узла** из адресной строки: `https://app.ilivemylife.io/item/ВОТ_ЭТОТ_ДЛИННЫЙ_КОД`
94
- 5. Вставь в `.env`: `NODE_EASY_APPLY_MAIN=скопированный_id`
95
-
96
- В **описании узла** (нажми на заголовок редактировать) напиши правила для бота:
97
-
98
- ```
99
- Правила для заполнения анкет:
100
- * Меня зовут Иван Петров
101
- * Мой город: Toronto, Ontario, Canada
102
- * Телефон: +1-416-xxx-xxxx
103
- * Email: ivan@gmail.com
104
- * Опыт программирования: 8 лет
105
- * Предпочитаю full-time, готов рассмотреть контракт
106
- * Я имею право работать в Канаде (PR)
107
- * Зарплатные ожидания: $120-150K CAD
108
- * Notice period: 14 дней
109
- ```
110
-
111
- > Lifebot читает описание узла и все дочерние узлы внутри него. Чем подробнее напишешь — тем точнее бот заполнит анкеты.
112
-
113
- #### 4. Создай дочерние узлы с информацией о себе
114
-
115
- Внутри **"LinkedIn Easy Apply"** создай несколько узлов-карточек. Lifebot будет обращаться к ним при заполнении анкет:
116
-
117
- **Узел "Резюме"** твой опыт работы:
118
- ```
119
- Senior Software Engineer, 8 лет опыта.
120
- Последний проект: ...
121
- Образование: ...
122
- Языки: английский (свободно), русский (родной)
123
- ```
124
- > Можно вставить подробно — все места работы, проекты, достижения. Или вместо создания нового узла — добавь ссылку на существующий узел с резюме, если он уже есть в графе.
125
-
126
- **Узел "Технологии и опыт"** — список навыков с годами:
127
- ```
128
- Java - 8 лет
129
- Python - 5 лет
130
- React - 4 года
131
- AWS - 6 лет
132
- Docker/K8s - 3 года
133
- ```
134
- > Когда анкета спросит "How many years of Python experience?" Lifebot найдёт ответ здесь.
135
-
136
- **Узел "Критерии подачи"** — на какие вакансии подавать:
137
- ```
138
- Подавать на: Software Engineer, Backend Developer, Full-stack, CTO
139
- НЕ подавать на: Sales, Marketing, Accounting, позиции не связанные с IT
140
- Локация: удалённо или Toronto
141
- ```
142
- > Бот перед каждой вакансией спрашивает Lifebot "Should I apply?". Этот узел помогает принять решение.
143
-
144
- **Узел "Error log"** сюда бот будет писать ошибки при заполнении форм:
145
- 1. Создай узел **"Error log"** внутри главного
146
- 2. AI-ассистент **не нужен** (это просто лог)
147
- 3. Скопируй ID в `.env`: `NODE_EASY_APPLY_ERRORS=скопированный_id`
148
-
149
- Итого структура будет такая:
150
- ```
151
- LinkedIn Easy Apply ← главный узел (AI включён)
152
- ├── Резюме ← опыт работы, образование
153
- ├── Технологии и опыт ← навыки с годами
154
- ├── Критерии подачи ← на что подавать / не подавать
155
- └── Error log ← сюда бот пишет ошибки
156
- ```
157
-
158
- #### 5. Настрой дешёвый AI (экономия)
159
-
160
- По умолчанию Lifebot использует дорогую модель. Для бота достаточно быстрой дешёвой — она отвечает моментально и бесплатного баланса хватит надолго.
161
-
162
- Откройте **основной узел** (LinkedIn Easy Apply) → **Настройки узла** (иконка шестерёнки):
163
-
164
- | Настройка | Значение | Зачем |
165
- |-----------|----------|-------|
166
- | **AI Quality** | **Fast/Cheap** | GPT-3.5 достаточно для анкет, экономит баланс |
167
- | **Root Access** | **Off** | Боту не нужен доступ к остальному графу |
168
-
169
- > Эти настройки влияют только на этот узел. Остальные узлы будут использовать вашу обычную модель.
170
-
171
- ### Шаг 6. Залогинься в LinkedIn
172
-
173
- ```
174
- npm run login
175
- ```
176
-
177
- Откроется браузер автоматически введёт логин/пароль. Если LinkedIn попросит подтверждение (SMS код, капча) — сделай это вручную. После входа браузер закроется.
178
-
179
- > Эту команду нужно запускать **один раз**. Повторно только если бот скажет "cookies expired".
180
-
181
- ### Готово!
182
-
183
- Теперь можешь использовать любые команды из раздела ниже.
184
-
185
- ---
186
-
187
- ## Обновление до новой версии
188
-
189
- Когда получишь новый `linkedin-auto-apply.zip`:
190
-
191
- ### Если настроен `DATA_DIR` (рекомендуется)
192
-
193
- Если в `.env` указан `DATA_DIR` данные хранятся отдельно от кода:
194
-
195
- 1. Распакуй новый zip в **новую папку**
196
- 2. Скопируй `.env` и `cookies.json` из старой папки
197
- 3. Запусти `npm install`
198
-
199
- Всё данные уже на месте, копировать ничего не нужно.
200
-
201
- ### Если `DATA_DIR` не настроен
202
-
203
- 1. Распакуй в **новую папку** (не поверх старой)
204
- 2. Скопируй из старой папки в новую:
205
- - папку `collected-profiles/` целиком (контакты, переписки, заявки)
206
- - папку `market-research/` целиком (разведка вакансий)
207
- - файл `.env` (настройки)
208
- - файл `cookies.json` (сессия LinkedIn)
209
- 3. Запусти `npm install`
210
-
211
- > **Совет:** настрой `DATA_DIR` в `.env` чтобы упростить будущие обновления (см. раздел "Путь к данным").
212
-
213
- ### После обновления
214
-
215
- Запусти любую команду данные автоматически обновятся до нового формата:
216
- ```
217
- npm run today
218
- ```
219
-
220
- > Сообщение `Schema migration: v0 v1` при первом запуске — это нормально. Бэкапы создаются автоматически.
221
-
222
- ---
223
-
224
- ## Основные команды
225
-
226
- ### Мониторинг входящих (рекомендую запускать каждый день)
227
-
228
- | Команда | Что делает |
229
- |---------|-----------|
230
- | `npm run sync-all` | **Главная команда.** Сканирует входящие, читает новые сообщения, обогащает данные |
231
- | `npm run sync-all:safe` | То же, но **не открывает непрочитанные** сохраняет unread-индикатор в LinkedIn UI |
232
- | `npm run today` | Показывает план на сегодня: кому ответить, что сделать (без браузера) |
233
- | `npm run sync-all:report` | Статистика без открытия браузера |
234
- | `npm run warm-scan` | Найти 1st-degree контактов в target-компаниях (без браузера) |
235
-
236
- ### Сообщения
237
-
238
- | Команда | Что делает |
239
- |---------|-----------|
240
- | `npm run messages -- --list` | Быстрая статистика бесед (без браузера) |
241
- | `npm run messages -- --read "Имя Фамилия"` | Прочитать всю переписку с человеком |
242
- | `npm run messages -- --send "Имя" "Текст"` | Отправить сообщение |
243
- | `npm run messages -- --draft "Имя" "Текст"` | Подготовить черновик (не отправляет) |
244
- | `npm run messages:review` | Просмотреть все черновики перед отправкой |
245
- | `npm run messages:push` | Отправить все подтверждённые черновики |
246
-
247
- ### Подача заявок на вакансии
248
-
249
- | Команда | Что делает |
250
- |---------|-----------|
251
- | `npm run apply` | Автоматическая подача на Easy Apply вакансии |
252
- | `npm run funnel` | Отправить connection requests рекрутерам |
253
- | `npm run sync` | Синхронизировать список контактов |
254
-
255
- ### Поиск по заявкам
256
-
257
- Перед собеседованием найди что ты отвечал при подаче:
258
-
259
- | Команда | Что делает |
260
- |---------|-----------|
261
- | `npm run search -- --company "Google"` | Все заявки в компанию |
262
- | `npm run search -- --job "senior engineer"` | Поиск по названию вакансии |
263
- | `npm run search -- --job-id 4387994033` | Все данные по конкретной вакансии |
264
-
265
- ---
266
-
267
- ## Ежедневный рабочий процесс
268
-
269
- ```
270
- npm run sync-all ← Утром: синхронизировать входящие
271
- npm run today ← Посмотреть что нужно сделать
272
- npm run apply ← Подать заявки на новые вакансии
273
- ```
274
-
275
- ---
276
-
277
- ## Настройки в .env
278
-
279
- ### Обязательные
280
-
281
- | Переменная | Пример | Описание |
282
- |---|---|---|
283
- | `LINKEDIN_NAME` | `John Smith` | Твоё имя как на LinkedIn (точное совпадение!) |
284
- | `LINKEDIN_LOGIN` | `john@gmail.com` | Email для входа |
285
- | `LINKEDIN_PASS` | `password123` | Пароль |
286
-
287
- ### Для подачи заявок (Easy Apply)
288
-
289
- | Переменная | Пример | Описание |
290
- |---|---|---|
291
- | `City` | `Toronto` | Город для форм |
292
- | `LINKEDIN_SEARCH_URL` | `https://www.linkedin.com/jobs/...` | URL поиска вакансий с настроенными фильтрами |
293
- | `MAX_APPLY_FOR_RUN` | `50` | Максимум заявок за один запуск |
294
- | `CV_TEXT` | `Check my CV...` | Текст для полей Cover Letter |
295
- | `RESUME_PDF_URL` | `https://...pdf` | Ссылка на PDF резюме |
296
- | `RESUME_FILENAME` | `John_Smith_Resume.pdf` | Имя файла резюме |
297
-
298
- ### Для iLiveMyLife (нужно для `npm run apply`)
299
-
300
- | Переменная | Описание |
301
- |---|---|
302
- | `NODE_EASY_APPLY_MAIN` | ID узла для решений бота (с AI-ассистентом!) |
303
- | `NODE_EASY_APPLY_ERRORS` | ID узла для логирования ошибок |
304
-
305
- > Токен подхватывается автоматически после `ilml login` (Шаг 5б). Прописывать в `.env` не нужно.
306
- >
307
- > **NODE_EASY_APPLY_MAIN обязателен** для подачи заявок (`npm run apply`). Бот использует AI для заполнения анкет.
308
- >
309
- > Мониторинг сообщений (`npm run sync-all`) работает без iLiveMyLife.
310
-
311
- ### Путь к данным (опционально)
312
-
313
- | Переменная | Описание |
314
- |---|---|
315
- | `DATA_DIR` | Папка для хранения данных. По умолчанию — рядом с кодом |
316
-
317
- Если хочешь хранить данные отдельно от кода (удобнее при обновлениях):
318
- ```ini
319
- DATA_DIR=D:\MyLinkedInData
320
- ```
321
- Бот создаст `collected-profiles/` и `market-research/` внутри этой папки.
322
-
323
- > **С `DATA_DIR` обновление проще:** распаковал новый zip → `npm install` → готово. Данные остались на месте.
324
-
325
- ---
326
-
327
- ## Где хранятся данные
328
-
329
- Все данные в папке `collected-profiles/` (создаётся автоматически):
330
-
331
- | Файл | Что внутри |
332
- |------|-----------|
333
- | `conversations.json` | Все переписки (сообщения, статусы, теги) |
334
- | `people.json` | Все контакты (имена, должности, компании) |
335
- | `jobs.json` | Поданные заявки |
336
- | `sync-state.json` | Когда последний раз синхронизировались |
337
-
338
- > **Бэкапы создаются автоматически** (`.backup` файлы). Если основной файл повредится — программа восстановит из бэкапа.
339
-
340
- ---
341
-
342
- ## Решение проблем
343
-
344
- | Ошибка | Что делать |
345
- |---|---|
346
- | `No iLiveMyLife token found` | Запусти `ilml login` (Шаг 5б) |
347
- | `NODE_EASY_APPLY_MAIN is not configured` | Создай узел с тегом `assist` и добавь ID в `.env` (Шаг 5б) |
348
- | `LINKEDIN_NAME is not set in .env` | Открой `.env` и добавь `LINKEDIN_NAME=Твоё Имя` |
349
- | `cookies.json not found` | Запусти `npm run login` |
350
- | `cookies expired` / `Redirected to login` | Запусти `npm run login` заново |
351
- | `cookies.json is corrupted` | Удали `cookies.json` и запусти `npm run login` |
352
- | `Schema migration: v0 → v1` | Нормально! При обновлении данные автоматически приводятся к новому формату |
353
- | `Lifebot timeout` | AI перегружен. Бот повторит 2 раза, потом пропустит вакансию. При 3 подряд — остановится, попробуй позже |
354
- | `npm` не найден | Установи Node.js (Шаг 1) |
355
- | Браузер не закрылся | Закрой его вручную (или нажми Ctrl+C в терминале) |
356
- | `Cannot find module` | Запусти `npm install` |
357
- | Данные пропали | Проверь папку `collected-profiles/` — там должны быть `.backup` файлы |
358
-
359
- ### Если ничего не помогает
360
-
361
- 1. Удали `cookies.json`
362
- 2. Запусти `npm run login`
363
- 3. Попробуй команду заново
364
-
365
- ---
366
-
367
- ## Дополнительные команды
368
-
369
- Полный список — в файле `COMMANDS.md`.
1
+ # ilml-plugin-linkedin
2
+
3
+ LinkedIn job-search and networking automation as a plugin for the [`ilml`](https://www.npmjs.com/package/@ilivemylife/graph-sdk) CLI.
4
+
5
+ - **npm:** https://www.npmjs.com/package/ilml-plugin-linkedin
6
+ - **Tracker / Releases (in iLiveMyLife):** open the **Releases** node inside the **LinkedIn Automation** project in your iLiveMyLife app
7
+ - **License:** UNLICENSED all rights reserved
8
+
9
+ ---
10
+
11
+ ## What it does
12
+
13
+ A toolkit that drives LinkedIn from your machine via a real browser (Puppeteer + stealth), with results synced into your [iLiveMyLife](https://ilivemylife.io) knowledge graph:
14
+
15
+ - **Easy Apply** — auto-fills and submits LinkedIn job applications. The graph's AI assistant (**Lifebot**) reads your profile node to answer custom application questions in your voice.
16
+ - **Inbox sync** — scans LinkedIn messages, detects changes, and mirrors threads into your graph.
17
+ - **Message bot** — draft → review → send batch messaging workflow (you approve before anything is sent).
18
+ - **Connection funnel** — process recruiter / founder / investor profiles in batches (connect, skip, message).
19
+ - **Scout** find jobs without applying; reports back which match your filters.
20
+ - **Profile activity** track who viewed your profile, who you visited, etc.
21
+
22
+ All persistent data (job DB, profile DB, conversation history, cookies) is stored locally in a directory you choose — never in a cloud you don't control.
23
+
24
+ ---
25
+
26
+ # User Guide
27
+
28
+ ## Prerequisites
29
+
30
+ - **Node.js 18+** [download](https://nodejs.org/) (the Long-Term Support version is fine).
31
+ - **iLiveMyLife account** — free signup at [iLiveMyLife.io](https://ilivemylife.io).
32
+ - **LinkedIn account** your personal one.
33
+
34
+ That's it. There's nothing to clone or compile.
35
+
36
+ ## Install
37
+
38
+ One-time, on your machine:
39
+
40
+ ```bash
41
+ # 1. Install the ilml CLI (skip if already installed)
42
+ npm install -g @ilivemylife/graph-sdk
43
+
44
+ # 2. Log in to your iLiveMyLife account
45
+ ilml login
46
+
47
+ # 3. Install this plugin
48
+ ilml plugin install linkedin
49
+ ```
50
+
51
+ That's the whole install. The bare-name `linkedin` resolves to `npm:ilml-plugin-linkedin` via the ilml plugin convention. The CLI downloads the latest version from npm, extracts it under `~/.ilivemylife/plugins/linkedin/`, and runs `npm install` for its runtime dependencies (mainly Puppeteer's bundled Chromium — first install takes a couple of minutes).
52
+
53
+ Verify it's installed:
54
+
55
+ ```bash
56
+ ilml plugin list
57
+ ilml linkedin
58
+ ```
59
+
60
+ ## Configure
61
+
62
+ The plugin needs a few things to know who you are and where to log activity. The first time you run any `ilml linkedin <command>`, the CLI will prompt you for the **required** fields. You can also pre-configure:
63
+
64
+ ```bash
65
+ ilml plugin config linkedin set LINKEDIN_NAME "Your Full Name"
66
+ ilml plugin config linkedin set NODE_EASY_APPLY_MAIN <node-id>
67
+ ilml plugin config linkedin set NODE_EASY_APPLY_ERRORS <node-id>
68
+ ```
69
+
70
+ To inspect (secret values are masked):
71
+
72
+ ```bash
73
+ ilml plugin config linkedin
74
+ ```
75
+
76
+ To change a value later or remove one:
77
+
78
+ ```bash
79
+ ilml plugin config linkedin set LINKEDIN_NAME "New Name"
80
+ ilml plugin config linkedin unset OPTIONAL_KEY
81
+ ilml plugin config linkedin reset # wipe all; will re-prompt next run
82
+ ```
83
+
84
+ ### Configuration fields
85
+
86
+ | Section | Field | Required | What it is |
87
+ |---|---|---|---|
88
+ | LinkedIn account | `LINKEDIN_NAME` | yes | Your full name **exactly** as it appears on LinkedIn (used to identify your own messages in threads). |
89
+ | | `LINKEDIN_LOGIN` | no | LinkedIn email — only used for auto-fill during one-time login. |
90
+ | | `LINKEDIN_PASS` | no, secret | LinkedIn password — only used for auto-fill during one-time login. Stored locally on disk; never sent anywhere. |
91
+ | iLiveMyLife integration | `NODE_EASY_APPLY_MAIN` | yes | Node ID where job applications are tracked. See "Setting up your graph" below. |
92
+ | | `NODE_EASY_APPLY_ERRORS` | yes | Node ID where Easy Apply errors are logged. |
93
+ | | `NODE_RUN_REPORTS` | no | Node ID where end-of-session summaries are posted (enable notifications on it to get pings when long jobs finish). |
94
+ | Data storage | `DATA_DIR` | no | Absolute path to a directory for the local DB / cookies. **Recommended** — keeps data outside the plugin install folder so it survives plugin updates. |
95
+ | Apply bot | `City` | no | Default city for filters. Default: `Toronto`. |
96
+ | | `MAX_APPLY_FOR_RUN` | no | Stop after this many applications per run. Default: `50`. |
97
+ | | `LINKEDIN_SEARCH_URL` | no | Default LinkedIn jobs search URL with your filters (use `f_AL=true` for Easy Apply). |
98
+ | Scouting | `LINKEDIN_SCOUT_URLS` | no | Comma-separated job search URLs to scan without applying. |
99
+ | Resume / CV | `CV_TEXT`, `RESUME_PDF_URL`, `RESUME_FILENAME` | no | CV snippet, public PDF URL, suggested filename. Used in messages to recruiters. |
100
+ | Funnel | `FUNNEL_USE_CUSTOM_MESSAGE` | no | Send a custom note with connection requests. Default: `false`. |
101
+ | | `FUNNEL_MAX_CONNECTIONS` | no | Cap connection requests per run. Default: `20`. |
102
+ | | `FUNNEL_MESSAGE` | no | Note text. Use `{name}` placeholder, e.g. `Hi {name}, I just applied for a role at your company`. |
103
+ | | `FUNNEL_EXCLUDE_LOCATIONS` | no | Comma-separated locations to skip. |
104
+
105
+ ## Setting up your graph
106
+
107
+ The Easy Apply bot uses iLiveMyLife's AI assistant (**Lifebot**) to answer custom application questions in your voice. Two graph nodes are required: a **main tracking node** with Lifebot enabled, and an **errors node** for failed applications.
108
+
109
+ 1. Open https://app.ilivemylife.io and create a node titled **"LinkedIn Easy Apply"** (or any name).
110
+ 2. Open the node's **settings** (right panel) and turn on **AI assistant**. This enables Lifebot for this node — without it, the bot can't answer questions and `ilml linkedin apply` will refuse to run.
111
+ 3. Copy the node ID from the URL: `https://app.ilivemylife.io/item/THIS-IS-THE-ID`.
112
+ 4. Set it: `ilml plugin config linkedin set NODE_EASY_APPLY_MAIN <id>`.
113
+ 5. In the node's **description**, write your answer rules — Lifebot reads this when filling forms. Example:
114
+
115
+ ```
116
+ Application question rules:
117
+ - My full name: Ivan Petrov
118
+ - City: Toronto, Ontario, Canada
119
+ - Phone: +1-416-xxx-xxxx
120
+ - Email: ivan@example.com
121
+ - Experience: Senior software engineer, 8 years
122
+ - Visa status: Permanent Resident, Canada
123
+ - Salary expectations: $120-150K CAD
124
+ - Notice period: 14 days
125
+ - Preferred work mode: hybrid or remote
126
+ ```
127
+
128
+ 6. (Optional but useful) Create child nodes inside "LinkedIn Easy Apply" with topical detail: `Resume`, `Tech stack`, `Interview availability`, etc. Lifebot reads the whole subtree and uses everything as context.
129
+
130
+ 7. Create a sibling node titled **"Easy Apply Errors"** (no AI needed). Copy its ID and set it: `ilml plugin config linkedin set NODE_EASY_APPLY_ERRORS <id>`.
131
+
132
+ ## First-time LinkedIn login
133
+
134
+ LinkedIn requires an authenticated session, so a one-time browser login is needed. Run:
135
+
136
+ ```bash
137
+ ilml linkedin login
138
+ ```
139
+
140
+ A real browser window opens. Sign in to LinkedIn (manually solve any captcha or 2FA). When you see your feed, close the window — your session cookies are saved locally for future runs.
141
+
142
+ ## Daily commands
143
+
144
+ | Command | What it does |
145
+ |---|---|
146
+ | `ilml linkedin apply` | Auto-apply to LinkedIn Easy Apply jobs. Stops after `MAX_APPLY_FOR_RUN`. |
147
+ | `ilml linkedin sync` / `sync-all` | Pull the LinkedIn inbox into the local DB and the graph. |
148
+ | `ilml linkedin today` | Faster sync of just today's activity. |
149
+ | `ilml linkedin enrich` | Categorize and tag conversations. |
150
+ | `ilml linkedin report` | Summary of the latest session. |
151
+ | `ilml linkedin scout` | Scan jobs against `LINKEDIN_SCOUT_URLS` without applying. |
152
+ | `ilml linkedin messages` | Batch messaging — draft, review, then push. Nothing sends without your approval. |
153
+ | `ilml linkedin funnel` | Process a queue of recruiter / founder / investor profiles. |
154
+ | `ilml linkedin visit` | Track profile visits. |
155
+ | `ilml linkedin viewers` | List who viewed your profile. |
156
+ | `ilml linkedin daily` | Compound routine combining several commands. |
157
+ | `ilml linkedin login` | Re-save LinkedIn cookies after a session expires. |
158
+
159
+ Run `ilml linkedin` (no subcommand) to see the list anytime.
160
+
161
+ ## Updating
162
+
163
+ Updates ship via npm. The CLI checks the registry and only downloads when there's a newer version:
164
+
165
+ ```bash
166
+ ilml plugin update linkedin # check this plugin
167
+ ilml plugin update # check all installed plugins
168
+ ilml update # update the SDK and all plugins together
169
+ ```
170
+
171
+ You'll see one of:
172
+ - `linkedin: already on latest version (1.2.1)` — nothing downloaded
173
+ - `linkedin: 1.2.1 → 1.3.0` followed by re-install — newer version pulled
174
+
175
+ Your saved configuration is preserved across updates (it lives outside the install dir). If a new version requires a new field, the next run will prompt you for just the new field.
176
+
177
+ ## Where data lives
178
+
179
+ | Path | What's there | Survives plugin update? | Cleared by `ilml logout`? |
180
+ |---|---|---|---|
181
+ | `~/.ilivemylife/plugins/linkedin/` | Plugin code (re-extracted on update) | Replaced | No |
182
+ | `~/.ilivemylife/plugins-state/linkedin.json` (or per-project local) | Your configured values | Yes | Yes (with that scope) |
183
+ | Directory you set as `DATA_DIR` | Local DBs, cookies, scraped profiles | Yes | No (you manage it) |
184
+
185
+ If you didn't set `DATA_DIR`, data files default to inside the plugin's install dir — meaning a plugin reinstall wipes them. Setting `DATA_DIR` to a sibling folder like `~/linkedin-plugin-data/` is strongly recommended.
186
+
187
+ ## Removing the plugin
188
+
189
+ ```bash
190
+ ilml plugin remove linkedin
191
+ ```
192
+
193
+ This wipes the install dir. To also clear your saved configuration in the active scope:
194
+
195
+ ```bash
196
+ ilml plugin config linkedin reset
197
+ ```
198
+
199
+ To clean up the data directory (cookies, DBs), delete the folder you set as `DATA_DIR` manually.
200
+
201
+ ---
202
+
203
+ # Developer Notes
204
+
205
+ > Single-developer project. The reference for plugin architecture and SDK contracts is the iLiveMyLife graph-sdk repo's [`PLUGINS.md`](https://github.com/iLiveMyLife/graph-sdk.iLiveMyLife.io/blob/main/PLUGINS.md). Most of what follows is just reminders for me when I come back to this in a few months.
206
+
207
+ ## Layout
208
+
209
+ ```
210
+ ilml-plugin.json # manifest: name, version, commands, config.sections[].fields[]
211
+ package.json # npm metadata; "files": ["dist", "ilml-plugin.json"] is the publish whitelist
212
+ .npmignore # belt-and-suspenders excludes (.env, cookies.json, sources, etc.)
213
+ .env / .env.example # dev only; gitignored
214
+
215
+ run.mjs / syncAll.mjs / messageBot.mjs / scout.mjs / ... # entry points (one per ilml subcommand)
216
+ src/
217
+ config.mjs # reads via SDK getPluginConfig(); merges into process.env so the rest of the codebase keeps using process.env.X
218
+ graphClient.mjs # SDK client wrapper
219
+ database.mjs # local JSON DB at DATA_DIR
220
+ saveLoginState.mjs # entry: ilml linkedin login
221
+ ... domain modules ...
222
+ scripts/
223
+ build-plugin.mjs # esbuild bundler — outputs dist/*.mjs (minified, no sourcemaps)
224
+ # externalizes: puppeteer, dotenv, @ilivemylife/graph-sdk
225
+ ```
226
+
227
+ ## Configuration model
228
+
229
+ - The plugin reads its config **only** through `getPluginConfig()` from `@ilivemylife/graph-sdk` (called once in `src/config.mjs`).
230
+ - Resolved values are merged into `process.env` so existing files reading `process.env.LINKEDIN_NAME` etc. keep working unchanged. New code should read directly from the SDK return value when feasible.
231
+ - Auth token: `process.env.ILML_TOKEN` injected by ilml CLI in production; resolved by the SDK in dev (local config / `.env` / global config / env var).
232
+ - Active scope dir: `process.env.ILML_SCOPE_DIR` set in production only. Plugin code never reads it directly; the SDK does.
233
+
234
+ ## Two run modes
235
+
236
+ | Mode | How invoked | Where config comes from |
237
+ |---|---|---|
238
+ | **Production** | `ilml linkedin <command>` | The active ilml scope's `<scope>/.ilivemylife/plugins-state/linkedin.json` |
239
+ | **Dev** | `node run.mjs` (etc.) | `.env` next to `ilml-plugin.json` (auto-detected by SDK) |
240
+
241
+ The plugin code is identical between the two. `ILML_SCOPE_DIR`'s presence is the only signal.
242
+
243
+ ## Local dev iteration
244
+
245
+ When the plugin code itself is what's changing:
246
+
247
+ ```bash
248
+ # 1. (optional) edit ilml-plugin.json or src/*
249
+ # 2. Build
250
+ npm run build:plugin
251
+
252
+ # 3a. Quick check via direct run (uses .env)
253
+ node run.mjs
254
+
255
+ # 3b. Full check via ilml CLI — needs the plugin re-packed and re-installed
256
+ npm pack
257
+ ilml plugin install linkedin file:///$(pwd)/ilml-plugin-linkedin-X.Y.Z.tgz
258
+ ilml linkedin <command>
259
+ ```
260
+
261
+ When the **SDK** is what's changing alongside, also rebuild and re-install the SDK from the sibling `graph-sdk.iLiveMyLife.io` repo:
262
+
263
+ ```bash
264
+ # In ../graph-sdk.iLiveMyLife.io
265
+ npm run build && npm pack
266
+
267
+ # In this repo
268
+ npm install ../graph-sdk.iLiveMyLife.io/ilivemylife-graph-sdk-X.Y.Z.tgz --no-save
269
+ npm run build:plugin
270
+ ```
271
+
272
+ ## Release process
273
+
274
+ 1. **Update `CHANGELOG.md`** — add an entry at the top describing what changed for users.
275
+ 2. **Bump version in BOTH** `package.json#version` AND `ilml-plugin.json#version`. They must match — `ilml plugin update` compares the manifest version against npm's `dist-tags.latest`. Drift here causes false update loops or silent skips.
276
+ 3. **Commit with explicit paths only:**
277
+ ```bash
278
+ git commit -- package.json ilml-plugin.json CHANGELOG.md <other-changed-files> -m "v1.X.Y: short description"
279
+ ```
280
+ 4. **Push:** `git push`.
281
+ 5. **Publish:** `npm publish --access public`. The `prepublishOnly` script runs `build:plugin` automatically — `dist/` is rebuilt from current sources before packing.
282
+ 6. **Update the Releases node in the graph** — sync description with the new CHANGELOG entry.
283
+
284
+ ## Security checklist before each `npm publish`
285
+
286
+ - `npm pack --dry-run` — verify only `dist/`, `ilml-plugin.json`, `package.json`, `README.md` end up in the tarball (~13 files). No `.env`, `cookies.json`, `*.json` data files, source `.mjs` at the repo root, `node_modules/`, internal handoff `.md`, `tmpclaude-*`, `.git/`.
287
+ - `grep -E "ilya|sorokin|password|token|@gmail" dist/*.mjs dist/src/*.mjs` — no real personal data should appear in bundled output. (Examples in `README.md` like `ivan@example.com` are fine — they're documentation placeholders.)
288
+ - `ls dist/*.map dist/**/*.map 2>/dev/null` — must return nothing (no sourcemaps).
289
+ - `npm whoami` confirm correct npm account before publish.
290
+
291
+ ## Useful pointers
292
+
293
+ - iLiveMyLife graph-sdk Plugin spec & 12 architecture rules: [`PLUGINS.md`](https://github.com/iLiveMyLife/graph-sdk.iLiveMyLife.io/blob/main/PLUGINS.md)
294
+ - SDK source: https://github.com/iLiveMyLife/graph-sdk.iLiveMyLife.io
295
+ - This plugin on npm: https://www.npmjs.com/package/ilml-plugin-linkedin
296
+ - ilml CLI on npm: https://www.npmjs.com/package/@ilivemylife/graph-sdk
297
+
298
+ ---
299
+
300
+ ## Author
301
+
302
+ Ilya Sorokin · [iLiveMyLife.io](https://ilivemylife.io) · info@ilivemylife.io
package/ilml-plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "LinkedIn job automation: apply, scout, sync, funnel, messages",
5
5
  "commands": {
6
6
  "apply": { "run": "node dist/run.mjs" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilml-plugin-linkedin",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "ilml plugin: LinkedIn job automation — apply, scout, sync, funnel, messages",
5
5
  "type": "module",
6
6
  "files": [