ilml-plugin-linkedin 1.2.0

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/README.md ADDED
@@ -0,0 +1,369 @@
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`.
@@ -0,0 +1,27 @@
1
+ var mt=Object.defineProperty;var Ie=(t,e)=>()=>(t&&(e=t(t=0)),e);var $e=(t,e)=>{for(var o in e)mt(t,o,{get:e[o],enumerable:!0})};var ze={};$e(ze,{addEncounter:()=>xe,canSendConnection:()=>Je,commitDataSnapshot:()=>Ge,default:()=>wt,getAllApplications:()=>Ve,getPersonStatus:()=>Le,getQuestionStats:()=>Qe,getQuotaStatus:()=>Ne,getRecentConnections:()=>ee,linkPersonToJob:()=>_e,loadApplicationQuestions:()=>de,loadConversations:()=>Ae,loadJobs:()=>H,loadPeople:()=>O,loadQuota:()=>Z,loadSyncState:()=>Ue,logApplicationQuestions:()=>Be,migrateFromDailyFiles:()=>Me,recordConnectionSent:()=>ke,resetCaches:()=>yt,saveConversations:()=>Re,saveJobs:()=>X,savePeople:()=>R,saveQuota:()=>fe,saveSyncState:()=>Pe,updateConnectionStatus:()=>Fe,upsertJob:()=>Ee,upsertPerson:()=>pe});import"dotenv/config";import*as d from"fs";import*as h from"path";import{fileURLToPath as ht}from"url";import{execSync as ie}from"child_process";function yt(){L=null,F=null,D=null,C=null,k=null,T=null}function y(){d.existsSync(S)||d.mkdirSync(S,{recursive:!0})}function A(t,e){let o=JSON.stringify(e,null,2),r=t+".tmp";d.writeFileSync(r,o);for(let n=0;n<3;n++)try{d.renameSync(r,t);return}catch(s){if(n<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function U(t,e){if(!d.existsSync(t))return null;try{let o=JSON.parse(d.readFileSync(t,"utf-8"));try{d.copyFileSync(t,t+".backup")}catch{}return o}catch(o){console.error(`[!] ${e} is corrupted: ${o.message}`),console.error(` File: ${h.resolve(t)}`);let r=t+".backup";if(d.existsSync(r))try{let n=JSON.parse(d.readFileSync(r,"utf-8"));console.error(` Restored from ${r}`);try{d.copyFileSync(r,t)}catch{}return n}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${t}.corrupted`);try{d.copyFileSync(t,t+".corrupted")}catch{}return null}}function J(){return new Date().toISOString().split("T")[0]}function O(){return L||(y(),L=U(ce,"people.json")||{},L)}function R(t){y(),L=t;try{A(ce,t)}catch(e){console.error(`[!] Failed to save people.json: ${e.message}`),console.error(` Path: ${h.resolve(ce)}`)}}function H(){return F||(y(),F=U(le,"jobs.json")||{},F)}function X(t){y(),F=t;try{A(le,t)}catch(e){console.error(`[!] Failed to save jobs.json: ${e.message}`),console.error(` Path: ${h.resolve(le)}`)}}function Ae(){return C||(y(),C=U(K,"conversations.json")||{},jt(C),C)}function jt(t){let e=0;for(let o of Object.values(t)){if(!(o.draftStatus!==void 0||o.draftPreparedAt!==void 0))continue;let n=(o.messages||[]).find(s=>s.status==="draft");n&&o.draftStatus&&o.draftStatus!=="pending"&&(n.status=o.draftStatus),delete o.draftStatus,delete o.draftPreparedAt,e++}if(e>0){console.log(` [migration] Moved draft state from conv to msg.status for ${e} conversations`);try{A(K,t)}catch{}}}function Re(t){y(),C=t;try{A(K,t)}catch(e){console.error(`[!] Failed to save conversations.json: ${e.message}`),console.error(` Path: ${h.resolve(K)}`)}}function pe(t,e={}){if(!t)return null;let o=O(),r=J(),n=new Date().toISOString(),s=o[t];if(s){s.seenCount=(s.seenCount||1)+1,s.lastSeen=r,s.lastUpdated=n,e.source&&!s.sources?.includes(e.source)&&(s.sources=s.sources||[],s.sources.push(e.source));for(let i of ue)e[i]!=null&&(s[i]=e[i])}else o[t]={name:e.name||null,title:e.title||null,location:e.location||null,company:e.company||null,connectionDegree:e.connectionDegree||null,connectionsCount:e.connectionsCount||null,mutualConnections:e.mutualConnections||null,photoUrl:e.photoUrl||null,hasVerifiedBadge:e.hasVerifiedBadge||!1,firstSeen:r,lastSeen:r,seenCount:1,sources:e.source?[e.source]:[],connectionStatus:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:n};return R(o),o[t]}function Ee(t,e={}){if(!t)return null;let o=H(),r=J(),n=o[t];if(n){let s=["title","company","companyUrl","location","salary","url","applied","appliedDate","recruiterUrl","description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","scoutedAt","lastSeen"];for(let i of s)e[i]!=null&&(n[i]=e[i])}else o[t]={title:e.title||null,company:e.company||null,location:e.location||null,salary:e.salary||null,url:e.url||null,applied:e.applied||!1,appliedDate:e.appliedDate||r,recruiterUrl:e.recruiterUrl||null,firstSeen:r};return X(o),o[t]}function _e(t,e){if(!t||!e)return;let o=O(),r=o[t];r&&(r.jobIds||(r.jobIds=[]),r.jobIds.includes(e)||(r.jobIds.push(e),r.lastUpdated=new Date().toISOString(),R(o)))}function xe(t,e){if(!t||!e)return;let o=O(),r=o[t];if(!r)return;r.encounters||(r.encounters=[]);let s={date:J(),source:e.source||"unknown",action:e.action||"unknown"};e.jobId&&(s.jobId=String(e.jobId)),e.jobTitle&&(s.jobTitle=e.jobTitle),e.jobCompany&&(s.jobCompany=e.jobCompany),!r.encounters.some(a=>a.date===s.date&&a.source===s.source&&a.action===s.action&&(a.jobId||"")===(s.jobId||""))&&(r.encounters.push(s),r.lastUpdated=new Date().toISOString(),R(o))}function Le(t){return t&&O()[t]?.connectionStatus||null}function Fe(t,e,o={}){if(!t)return;let r=O(),n=r[t];if(n||(pe(t,o.profileInfo||{}),n=r[t]),!!n){if(n.connectionStatus=e,n.connectionDate=J(),n.connectionMethod=o.method||n.connectionMethod,n.messageSent=o.messageSent||n.messageSent,n.lastUpdated=new Date().toISOString(),o.profileInfo)for(let s of ue)o.profileInfo[s]!=null&&(n[s]=o.profileInfo[s]);R(r)}}function Z(){return D||(y(),D=U(Te,"quota.json"),(!D||!D.connections)&&(D={connections:[]}),D)}function fe(t){y(),D=t;try{A(Te,t)}catch(e){console.error(`[!] Failed to save quota.json: ${e.message}`)}}function ke(t,e="unknown"){let o=Z(),r=new Date;o.connections.push({date:r.toISOString().split("T")[0],time:r.toTimeString().split(" ")[0],profileUrl:t,source:e}),fe(o)}function ee(){let t=Z(),e=new Date;e.setDate(e.getDate()-7);let o=e.toISOString().split("T")[0];return t.connections.filter(r=>r.date>=o)}function Je(){let t=ee(),e=J(),o=t.filter(u=>u.date===e).length,r=t.length,n=o>=N,s=r>=M,i=!n&&!s,a={allowed:i,daily:{sent:o,max:N},weekly:{sent:r,max:M}};return i||(a.reason=n?`Daily limit reached (${o}/${N})`:`Weekly limit reached (${r}/${M})`),a}function Ne(){let t=ee(),e=J(),o=t.filter(n=>n.date===e).length,r=t.length;return{daily:{sent:o,max:N,remaining:N-o},weekly:{sent:r,max:M,remaining:M-r}}}function bt(t){if(!t)return null;let e=t.match(/jobs\/view\/(\d+)/);return e?e[1]:null}function Me(){y();let t=d.readdirSync(S).filter(s=>s.endsWith(".json")&&s!=="people.json"&&s!=="jobs.json"&&s!=="quota.json");if(t.length===0)return console.log("No daily files found to migrate."),{people:0,jobs:0};console.log(`
2
+ Migrating from ${t.length} daily files...
3
+ `);let e=O(),o=H(),r=0,n=0;t.sort();for(let s of t){let i=h.join(S,s),a;try{a=JSON.parse(d.readFileSync(i,"utf-8"))}catch(c){console.log(` Skipping ${s}: ${c.message}`);continue}let u=a.date||s.match(/\d{4}-\d{2}-\d{2}/)?.[0]||null,p=a.source||s.split("-").slice(0,-1).join("-").replace(/-\d{4}$/,"")||"unknown";if(!a.profiles||!Array.isArray(a.profiles)){console.log(` Skipping ${s}: no profiles array`);continue}console.log(` ${s}: ${a.profiles.length} profiles`);for(let c of a.profiles){let m=c.profileUrl;if(!m)continue;let l=e[m];if(l){l.seenCount=(l.seenCount||1)+1,u&&(!l.lastSeen||u>l.lastSeen)&&(l.lastSeen=u),u&&(!l.firstSeen||u<l.firstSeen)&&(l.firstSeen=u),p&&!l.sources?.includes(p)&&(l.sources=l.sources||[],l.sources.push(p));for(let f of ue)c[f]!=null&&(l[f]=c[f]);c.status&&c.processed&&(l.connectionStatus=c.status,l.connectionDate=u,c.funnelResult&&(l.connectionMethod=c.funnelResult.method||l.connectionMethod,l.messageSent=c.funnelResult.message||l.messageSent)),l.lastUpdated=new Date().toISOString()}else e[m]={name:c.name||null,title:c.title||null,location:c.location||null,company:c.company||null,connectionDegree:c.connectionDegree||null,connectionsCount:c.connectionsCount||null,mutualConnections:c.mutualConnections||null,photoUrl:c.photoUrl||null,hasVerifiedBadge:c.hasVerifiedBadge||!1,firstSeen:u,lastSeen:u,seenCount:1,sources:p?[p]:[],connectionStatus:c.processed&&c.status||null,connectionDate:c.processed?u:null,connectionMethod:c.funnelResult?.method||null,messageSent:c.funnelResult?.message||null,jobIds:[],lastUpdated:new Date().toISOString()},r++;if(c.fromJob?.url){let f=bt(c.fromJob.url);f&&(e[m].jobIds||(e[m].jobIds=[]),e[m].jobIds.includes(f)||e[m].jobIds.push(f),o[f]?o[f].recruiterUrl||(o[f].recruiterUrl=m):(o[f]={title:c.fromJob.title||null,company:c.fromJob.company||null,location:null,salary:null,url:c.fromJob.url,applied:!0,appliedDate:u,recruiterUrl:m,firstSeen:u},n++))}}}return R(e),X(o),console.log(`
4
+ Migration complete!`),console.log(` People: ${Object.keys(e).length} total (${r} new)`),console.log(` Jobs: ${Object.keys(o).length} total (${n} new)`),{people:Object.keys(e).length,jobs:Object.keys(o).length}}function Ue(){return k||(y(),k=U(Ce,"sync-state.json")||{},k)}function Pe(t){y(),k=t;try{A(Ce,t)}catch(e){console.error(`[!] Failed to save sync-state.json: ${e.message}`)}}function de(){if(T)return T;y();try{d.existsSync(ae)&&(T=JSON.parse(d.readFileSync(ae,"utf-8")))}catch{}return T||(T={}),T}function qe(t){return Array.isArray(t.applications)}function Ve(){let t=de();if(qe(t))return t.applications;let e=[];for(let o of Object.values(t))o.applications&&e.push(...o.applications);return e}function Be(t){if(!t||!t.questions||t.questions.length===0)return;let e=de(),o=t.jobId||"unknown",n={applicationId:`${o}_${Date.now()}`,date:new Date().toISOString(),result:t.result||"submitted",formPages:t.formPages||null,duration:t.duration||null,retries:t.retries||0,failedField:t.failedField||null,questionCount:t.questions.length,questions:t.questions.map(s=>{let i={type:s.type||"input",question:s.labelText||s.question||"",answer:s.answer||""};return s.options&&(s.options.length<=vt?i.options=s.options:i.optionsOmitted=!0),s.page&&(i.page=s.page),s.wasPreFilled&&(i.wasPreFilled=!0),s.wasRetry&&(i.wasRetry=!0),i})};qe(e)?(n.jobId=o,n.jobTitle=t.jobTitle||null,n.jobCompany=t.jobCompany||null,n.jobUrl=t.jobUrl||null,e.applications.push(n)):(e[o]||(e[o]={applications:[]}),e[o].applications.push(n));try{A(ae,e)}catch(s){console.error(`[!] Failed to save application-questions.json: ${s.message}`)}}function Qe(){let t=Ve(),e=new Map;for(let s of t)for(let i of s.questions||[]){let a=i.question.toLowerCase().trim();if(!a)continue;e.has(a)||e.set(a,{question:i.question,count:0,answers:new Map});let u=e.get(a);u.count++;let p=(i.answer||"").trim();u.answers.set(p,(u.answers.get(p)||0)+1)}let o=[...e.values()].sort((s,i)=>i.count-s.count),r=o.slice(0,15).map(s=>({question:s.question,count:s.count,topAnswer:[...s.answers.entries()].sort((i,a)=>a[1]-i[1])[0]?.[0]||""})),n=o.filter(s=>s.answers.size>1&&s.count>=3).map(s=>({question:s.question,count:s.count,answers:[...s.answers.entries()].sort((i,a)=>a[1]-i[1]).map(([i,a])=>`${i} (${a}x)`)}));return{totalApplications:t.length,uniqueQuestions:e.size,topQuestions:r,inconsistent:n}}function Ge(t){if(d.existsSync(h.join(S,".git")))try{let e={cwd:S,stdio:"pipe",timeout:1e4};ie("git add conversations.json people.json jobs.json quota.json sync-state.json run-log.json application-questions.json",e);try{ie("git diff --cached --quiet",e);return}catch{}let o=(t||`sync ${new Date().toISOString().slice(0,16)}`).replace(/"/g,'\\"');ie(`git commit -m "${o}"`,e)}catch(e){process.env.VERBOSE&&console.log(` [git] snapshot skipped: ${e.message?.slice(0,80)}`)}}var gt,St,S,ce,le,Te,K,Ce,ae,N,M,L,F,D,C,k,T,ue,vt,wt,me=Ie(()=>{gt=ht(import.meta.url),St=h.dirname(h.dirname(gt)),S=process.env.DATA_DIR?h.resolve(process.env.DATA_DIR,"collected-profiles"):h.join(St,"collected-profiles"),ce=h.join(S,"people.json"),le=h.join(S,"jobs.json"),Te=h.join(S,"quota.json"),K=h.join(S,"conversations.json"),Ce=h.join(S,"sync-state.json"),ae=h.join(S,"application-questions.json"),N=20,M=100,L=null,F=null,D=null,C=null,k=null,T=null;ue=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge"];vt=50;wt={loadPeople:O,savePeople:R,loadJobs:H,saveJobs:X,loadConversations:Ae,saveConversations:Re,upsertPerson:pe,upsertJob:Ee,linkPersonToJob:_e,addEncounter:xe,getPersonStatus:Le,updateConnectionStatus:Fe,migrateFromDailyFiles:Me,loadQuota:Z,saveQuota:fe,recordConnectionSent:ke,canSendConnection:Je,getRecentConnections:ee,getQuotaStatus:Ne,loadSyncState:Ue,saveSyncState:Pe,commitDataSnapshot:Ge,logApplicationQuestions:Be,getQuestionStats:Qe}});var et={};$e(et,{getJobDecision:()=>Ct,getLastVisit:()=>_t,getScoutStats:()=>xt,loadCompanies:()=>Ze,loadDiscoveredPeople:()=>ge,loadJobDecisions:()=>Y,loadScoutedJobs:()=>W,loadVisitLog:()=>te,recordVisit:()=>Et,resetCaches:()=>$t,saveCompany:()=>Rt,saveDiscoveredPerson:()=>At,saveScoutedJob:()=>he,setJobDecision:()=>Tt});import"dotenv/config";import*as v from"fs";import*as j from"path";import{fileURLToPath as Ot}from"url";function Q(){v.existsSync(_)||v.mkdirSync(_,{recursive:!0})}function G(t,e){let o=JSON.stringify(e,null,2),r=t+".tmp";v.writeFileSync(r,o);for(let n=0;n<3;n++)try{v.renameSync(r,t);return}catch(s){if(n<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function z(t){try{if(v.existsSync(t))return JSON.parse(v.readFileSync(t,"utf-8"))}catch{}return null}function $t(){P=null,q=null,V=null,B=null,E=null}function W(){return P||(P=z(We)||{},P)}function he(t,e){if(!t)return;Q();let o=W(),r=new Date().toISOString().slice(0,10);if(o[t]){for(let[n,s]of Object.entries(e))s!=null&&(o[t][n]=s);o[t].lastSeen=r}else o[t]={...e,scoutedAt:r,lastSeen:r};try{G(We,o)}catch(n){console.error(`[!] Failed to save scouted-jobs.json: ${n.message}`)}}function Y(){return q||(q=z(Xe)||{},q)}function Tt(t,e,o={}){if(!t||!e)return;Q();let r=Y(),n=new Date().toISOString().slice(0,10);r[t]?(r[t].status=e,r[t].updatedAt=n,o.reason&&(r[t].reason=o.reason),o.score!=null&&(r[t].score=o.score),o.source&&(r[t].source=o.source)):r[t]={status:e,decidedAt:n,updatedAt:n,reason:o.reason||null,score:o.score??null,source:o.source||null};try{G(Xe,r)}catch(s){console.error(`[!] Failed to save job-decisions.json: ${s.message}`)}}function Ct(t){return Y()[t]||null}function ge(){return V||(V=z(Ye)||{},V)}function At(t,e){if(!t)return;Q();let o=ge(),r=new Date().toISOString().slice(0,10);if(o[t]){let n=o[t];e.name&&(n.name=e.name),e.title&&(n.title=e.title),e.company&&(n.company=e.company),e.location&&(n.location=e.location),e.role&&(n.role=e.role),e.isConnected!=null&&(n.isConnected=e.isConnected),e.jobId&&(n.jobIds||(n.jobIds=[]),n.jobIds.includes(e.jobId)||n.jobIds.push(e.jobId)),n.lastSeen=r,n.seenCount=(n.seenCount||1)+1}else o[t]={name:e.name||null,title:e.title||null,company:e.company||null,location:e.location||null,role:e.role||null,source:e.source||"scout",isConnected:e.isConnected||!1,discoveredAt:r,lastSeen:r,seenCount:1,jobIds:e.jobId?[e.jobId]:[]};try{G(Ye,o)}catch(n){console.error(`[!] Failed to save discovered-people.json: ${n.message}`)}}function Ze(){return B||(B=z(He)||{},B)}function Rt(t,e){if(!t)return;Q();let o=Ze(),r=new Date().toISOString().slice(0,10);if(o[t]){let n=o[t];e.name&&(n.name=e.name),e.about&&(n.about=e.about),e.size&&(n.size=e.size),e.industries&&(n.industries=e.industries),e.jobId&&(n.jobIds||(n.jobIds=[]),n.jobIds.includes(e.jobId)||n.jobIds.push(e.jobId)),e.recruiterUrl&&(n.recruiterUrls||(n.recruiterUrls=[]),n.recruiterUrls.includes(e.recruiterUrl)||n.recruiterUrls.push(e.recruiterUrl)),n.lastSeen=r,n.jobCount=n.jobIds?.length||0}else o[t]={name:e.name||null,about:e.about||null,size:e.size||null,industries:e.industries||null,discoveredAt:r,lastSeen:r,jobIds:e.jobId?[e.jobId]:[],recruiterUrls:e.recruiterUrl?[e.recruiterUrl]:[],jobCount:e.jobId?1:0};try{G(He,o)}catch(n){console.error(`[!] Failed to save companies.json: ${n.message}`)}}function te(){return E||(E=z(Ke)||{visits:[]},Array.isArray(E.visits)||(E.visits=[]),E)}function Et(t,e={}){if(!t)return;Q();let o=te();o.visits.push({date:new Date().toISOString(),profileUrl:t,name:e.name||null,source:e.source||"manual"});let r=Date.now()-90*24*60*60*1e3;o.visits=o.visits.filter(n=>new Date(n.date).getTime()>r);try{G(Ke,o)}catch(n){console.error(`[!] Failed to save visit-log.json: ${n.message}`)}}function _t(t){let e=te();for(let o=e.visits.length-1;o>=0;o--)if(e.visits[o].profileUrl===t)return e.visits[o].date;return null}function xt(){let t=W(),e=ge(),o=Object.values(t),r=Object.values(e),n={};for(let l of o)l.company&&(n[l.company]=(n[l.company]||0)+1);let s=Object.entries(n).sort((l,f)=>f[1]-l[1]).slice(0,5).map(([l,f])=>`${l} (${f})`),i={};for(let l of o)for(let f of l.techStack||[])i[f]=(i[f]||0)+1;let a=Object.entries(i).sort((l,f)=>f[1]-l[1]).slice(0,8).map(([l,f])=>`${l} (${f})`),u=te(),p=new Date().toISOString().slice(0,10),c=u.visits.filter(l=>l.date.startsWith(p)).length,m=new Set(u.visits.map(l=>l.profileUrl)).size;return{totalJobs:o.length,totalRecruiters:r.length,topCompanies:s,topTech:a,totalVisited:m,todayVisited:c,lastScoutDate:o.length>0?o.sort((l,f)=>(f.scoutedAt||"").localeCompare(l.scoutedAt||""))[0]?.scoutedAt:null}}var Dt,It,_,We,Ye,Ke,He,Xe,P,q,V,B,E,oe=Ie(()=>{Dt=Ot(import.meta.url),It=j.dirname(j.dirname(Dt)),_=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(It,"market-research"),We=j.join(_,"scouted-jobs.json"),Ye=j.join(_,"discovered-people.json"),Ke=j.join(_,"visit-log.json"),He=j.join(_,"companies.json"),Xe=j.join(_,"job-decisions.json");P=null;q=null;V=null;B=null;E=null});import"dotenv/config";import*as $ from"fs";import*as x from"path";import{fileURLToPath as to}from"url";import{execFileSync as oo}from"child_process";me();oe();var Lt=new Set(["java","c#",".net","scala","python","typescript","javascript","react","node.js","aws","azure","docker","kubernetes","microservices","graphql","rest","sql","postgresql","mongodb","kafka","redis","ci/cd","terraform","spring"]),Ft=new Set(["iot","machine learning","ai","data engineering","spark","go","rust","kotlin","next.js","devops","sre"]),tt=["vancouver","british columbia"],kt=["canada","montreal","quebec","alberta","calgary","ottawa"],Jt=["toronto","ontario"],Nt=["india","pakistan","nigeria","philippines"];function Mt(t,e={}){let o=0,r=(t.techStack||[]).map(c=>c.toLowerCase()),n=0,s=0;for(let c of r)Lt.has(c)&&n++,Ft.has(c)&&s++;if(o+=Math.min(n*6,24),o+=Math.min(s*3,6),t.salary){let c=Ut(t.salary);c&&(c>=15e4?o+=20:c>=12e4?o+=15:c>=1e5?o+=10:c>=8e4?o+=5:c>=100&&c<1e3?o+=15:c>=70&&c<1e3&&(o+=10))}let i=(t.location||"").toLowerCase(),a=/remote/i.test(i)||t.workplaceType==="remote";Nt.some(c=>i.includes(c))?o-=30:a&&/united states|usa|\bus\b/i.test(i)?o+=25:a&&tt.some(c=>i.includes(c))?o+=22:a?o+=20:tt.some(c=>i.includes(c))?o+=18:kt.some(c=>i.includes(c))?o+=10:Jt.some(c=>i.includes(c))&&(o+=5),t.isEasyApply&&(o+=5);let u=(t.seniority||"").toLowerCase();u.includes("senior")||u.includes("lead")||u.includes("staff")?o+=10:u.includes("mid")?o+=5:(u.includes("entry")||u.includes("intern"))&&(o-=10),t.recruiterUrl&&(e.conversationRecruiters?.has(t.recruiterUrl)?o+=20:e.connectedRecruiters?.has(t.recruiterUrl)?o+=15:o+=3);let p=new Set(t.jobTags||[]);return p.has("managerial")?o+=8:p.has("senior-ic")&&(o+=5),p.has("junior")&&(o-=15),p.has("consultant")&&(o-=3),Math.max(0,Math.min(100,o))}function Se(t=10,e={}){let{easyApplyOnly:o=!0}=e,r=W(),n=O(),s=new Set,i=new Set;for(let[m,l]of Object.entries(n))l.connectionStatus==="connected"&&s.add(m);let a={connectedRecruiters:s,conversationRecruiters:i},u=Y(),p=new Set(["applied","skipped","expired","archived"]);return Object.entries(r).filter(([m,l])=>{if(typeof l!="object"||o&&!l.isEasyApply||l.applied)return!1;let f=u[m];return!(f&&p.has(f.status))}).map(([m,l])=>({jobId:m,score:Mt(l,a),job:l})).sort((m,l)=>{if(l.score!==m.score)return l.score-m.score;let f=m.job.scoutedAt||m.job.postedTime||"";return(l.job.scoutedAt||l.job.postedTime||"").localeCompare(f)}).slice(0,t)}function Ut(t){if(!t)return null;let e=t.match(/[\$£€CAD\s]*([\d,]+(?:\.\d+)?)\s*[kK]?/g);if(!e||e.length===0)return null;let o=e.map(r=>{let n=parseFloat(r.replace(/[^\d.]/g,""));return r.toLowerCase().includes("k")&&(n*=1e3),n}).filter(r=>r>0);return o.length>=2?(o[0]+o[1])/2:o.length===1?o[0]:null}oe();import"dotenv/config";import*as w from"fs";import*as I from"path";import{fileURLToPath as Gt}from"url";import"dotenv/config";import{createGraphClient as Pt,resolveToken as qt}from"@ilivemylife/graph-sdk";import{config as Vt}from"dotenv";Vt();var ot=qt()||process.env.TOKEN;ot||(console.error(`
5
+ [FATAL] No iLiveMyLife token found.`),console.error(" The bot uses Lifebot AI to decide which jobs to apply for"),console.error(` and how to fill application forms. It cannot work without it.
6
+ `),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
7
+ `),console.error(` Or add ILML_TOKEN=... to .env
8
+ `),process.exit(1));var nt=Pt({token:ot});function Bt(t){if(!t||t<1e3)return`${t||0}ms`;let e=Math.round(t/1e3);if(e<60)return`${e}s`;let o=Math.floor(e/60),r=e%60;return r?`${o}m ${r}s`:`${o}m`}function Qt({scriptName:t,mode:e,status:o,error:r,duration:n}){let s=o==="error"?"\u2717":o==="quota-reached"?"\u26A0":"\u2713",i=new Date().toISOString().slice(0,16).replace("T"," "),a=n?` ${Bt(n)}`:"",u=o==="error"?`error${r?`: ${String(r).slice(0,80)}`:""}`:o==="quota-reached"?"daily quota reached":"done",p=e&&e!=="default"?` (${e})`:"";return`${s} ${t}${p} \u2014 ${u} \u2014 ${i}${a}`}async function st({scriptName:t,mode:e,status:o,summaryLines:r,error:n,duration:s}){let i=process.env.NODE_RUN_REPORTS;if(!i||!Array.isArray(r)||r.length===0)return;let u=`**${Qt({scriptName:t,mode:e,status:o,error:n,duration:s})}**
9
+
10
+ ${r.join(`
11
+ `)}`;try{await nt.addMessage(i,u)}catch(p){console.error(`[runReport] Failed to post end-of-session message: ${p.message}`)}}var zt=Gt(import.meta.url),Wt=I.dirname(I.dirname(zt)),ye=process.env.DATA_DIR?I.resolve(process.env.DATA_DIR,"collected-profiles"):I.join(Wt,"collected-profiles"),je=I.join(ye,"run-log.json");function Yt(t,e){let o=JSON.stringify(e,null,2),r=t+".tmp";w.writeFileSync(r,o);for(let n=0;n<3;n++)try{w.renameSync(r,t);return}catch(s){if(n<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function rt(){try{if(!w.existsSync(je))return{runs:[]};let t=JSON.parse(w.readFileSync(je,"utf-8"));return!t.runs||!Array.isArray(t.runs)?{runs:[]}:t}catch{return{runs:[]}}}function it(t){w.existsSync(ye)||w.mkdirSync(ye,{recursive:!0});try{Yt(je,t)}catch(e){console.error(`[!] Failed to save run-log.json: ${e.message}`)}}function ct(t,e="default"){let o=rt();for(let i of o.runs)i.status==="running"&&(i.status="interrupted",i.result="interrupted",i.timestamp&&(i.duration=Date.now()-new Date(i.timestamp).getTime()));let r=new Date,n={timestamp:r.toISOString(),script:t,mode:e,status:"running",result:null,duration:null,stats:null,error:null};o.runs.push(n);let s=Date.now()-30*24*60*60*1e3;return o.runs=o.runs.filter(i=>new Date(i.timestamp).getTime()>s),it(o),{script:t,mode:e,startTime:r.getTime(),runIndex:o.runs.length-1}}function be(t,e,o=null,r=null){if(!t)return;let n=null;try{let i=rt(),a=i.runs[t.runIndex];if(a&&a.status==="running"&&a.script===t.script)a.status=e,a.result=e,a.duration=Date.now()-t.startTime,a.stats=o,a.error=r,n=a.duration;else{let u=i.runs.find(p=>p.script===t.script&&p.status==="running"&&p.timestamp===new Date(t.startTime).toISOString());u&&(u.status=e,u.result=e,u.duration=Date.now()-t.startTime,u.stats=o,u.error=r,n=u.duration)}it(i)}catch(i){console.error(`[runLog] Failed to end run: ${i.message}`)}let s=o&&Array.isArray(o.summaryLines)?o.summaryLines:null;if(s&&s.length>0&&process.env.NODE_RUN_REPORTS){let i=e==="error"?"error":o&&o.dailyLimitHit?"quota-reached":"done";st({scriptName:t.script,mode:t.mode,status:i,summaryLines:s,error:r,duration:n??Date.now()-t.startTime}).catch(a=>console.error(`[runLog] postRunReport failed: ${a.message}`))}}import"dotenv/config";import*as b from"fs";import*as g from"path";import{fileURLToPath as Kt}from"url";var Ht=Kt(import.meta.url),Xt=g.dirname(Ht),ut=g.dirname(g.dirname(Xt)),ne=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(ut,"collected-profiles"),se=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"market-research"):g.join(ut,"market-research"),ve=1,Zt=[{version:1,file:"./v001-normalize-jobs-and-questions.mjs"}];function pt(t){return g.join(t,".schema-version")}function we(t){let e=pt(t);if(!b.existsSync(e))return{version:0,migrations:[]};try{return JSON.parse(b.readFileSync(e,"utf-8"))}catch{return{version:0,migrations:[]}}}function eo(t,e){b.writeFileSync(pt(t),JSON.stringify(e,null,2))}function lt(t,e){if(!b.existsSync(t))return;let o=`.pre-v${String(e).padStart(3,"0")}.backup`,r=b.readdirSync(t).filter(n=>n.endsWith(".json")&&!n.includes(".backup"));for(let n of r){let s=g.join(t,n),i=g.join(t,n+o);try{b.copyFileSync(s,i)}catch(a){console.error(` [!] Failed to backup ${n}: ${a.message}`)}}r.length>0&&console.log(` Backed up ${r.length} files in ${g.basename(t)}/ (${o})`)}var at=!1;async function ft(){if(at)return;at=!0;let t=we(ne),e=we(se),o=Math.min(t.version,e.version);if(o>=ve)return;console.log(`
12
+ \u{1F4E6} Schema migration: v${o} \u2192 v${ve}`);let r=Zt.filter(n=>n.version>o);for(let n of r){console.log(`
13
+ Running migration v${String(n.version).padStart(3,"0")}...`),lt(ne,n.version),lt(se,n.version);let s=await import(n.file);try{await s.up(ne,se)}catch(u){console.error(`
14
+ [FATAL] Migration v${n.version} failed: ${u.message}`),console.error(` Backups are in collected-profiles/ and market-research/ (.pre-v${String(n.version).padStart(3,"0")}.backup)`),console.error(` Fix the issue and restart.
15
+ `),process.exit(1)}let i=new Date().toISOString(),a={version:n.version,name:s.NAME||`v${String(n.version).padStart(3,"0")}`,appliedAt:i};for(let u of[ne,se]){if(!b.existsSync(u))continue;let p=we(u);p.version=n.version,p.migratedAt=i,p.migrations.push(a),eo(u,p)}console.log(` \u2713 Migration v${String(n.version).padStart(3,"0")} complete`)}try{let{resetCaches:n}=await Promise.resolve().then(()=>(me(),ze));n()}catch{}try{let{resetCaches:n}=await Promise.resolve().then(()=>(oe(),et));n()}catch{}console.log(`
16
+ \u2713 Schema is now at v${ve}
17
+ `)}var no=to(import.meta.url),so=x.dirname(no),Oe=process.env.DATA_DIR?x.resolve(process.env.DATA_DIR,"collected-profiles"):x.join(so,"collected-profiles");await ft();var De=process.argv.slice(2),dt=parseInt(De.find(t=>t.startsWith("--max="))?.split("=")[1]||"50"),re=parseInt(De.find(t=>t.startsWith("--min-score="))?.split("=")[1]||"50"),ro=De.includes("--list");function io(){let e=Se(20,{easyApplyOnly:!0}).filter(o=>o.score>=re);if(console.log(`
18
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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(` APPLY QUEUE (${e.length} jobs, min score ${re})`),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"),e.length===0)console.log(" No jobs in queue. Run: npm run scout");else for(let o=0;o<e.length;o++){let{score:r,job:n,jobId:s}=e[o],i=(n.techStack||[]).slice(0,4).join(", ");console.log(` ${String(o+1).padStart(2)}. [${r}] ${(n.title||"?").slice(0,35)} @ ${(n.company||"?").slice(0,20)}`),console.log(` ${n.salary||"no salary"} | ${n.location||"?"} | ${i||"no tech"}`),console.log(` ${n.url}`)}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
19
+ `)}async function co(){if(console.log(`
20
+ LinkedIn Apply Queue
21
+ `),ro){io();return}let e=Se(dt*3,{easyApplyOnly:!0}).filter(s=>s.score>=re).slice(0,dt);if(e.length===0){console.log(` No jobs to apply. Run: npm run scout
22
+ `);return}console.log(` Applying to ${e.length} jobs (score >= ${re}):
23
+ `);for(let{score:s,job:i}of e)console.log(` [${s}] ${(i.title||"?").slice(0,40)} @ ${(i.company||"?").slice(0,20)}`);$.existsSync(Oe)||$.mkdirSync(Oe,{recursive:!0});let o=x.join(Oe,"apply-queue-urls.json"),r=e.map(({job:s,jobId:i})=>({url:s.url,title:s.title,company:s.company,location:s.location,salary:s.salary}));$.writeFileSync(o,JSON.stringify(r,null,2)),console.log(`
24
+ Generated ${o} with ${r.length} jobs`);for(let{jobId:s}of e)he(s,{queued:!0,queuedAt:new Date().toISOString().slice(0,10)});let n=ct("apply-queue",`${e.length} jobs`);console.log(`
25
+ Launching run.mjs --job-urls...
26
+ `);try{oo("node",["run.mjs",`--job-urls=${o}`],{stdio:"inherit",timeout:144e5}),be(n,"success",{applied:e.length})}catch(s){console.error(`
27
+ Apply queue error: ${s.message?.slice(0,80)}`),be(n,"error",{applied:e.length},s.message)}try{$.unlinkSync(o)}catch{}}co().catch(t=>{console.error("Apply queue failed:",t),process.exit(1)});
package/dist/daily.mjs ADDED
@@ -0,0 +1,22 @@
1
+ var ht=Object.defineProperty;var De=(t,e)=>()=>(t&&(e=t(t=0)),e);var Ie=(t,e)=>{for(var o in e)ht(t,o,{get:e[o],enumerable:!0})};var Ge={};Ie(Ge,{addEncounter:()=>Ne,canSendConnection:()=>ee,commitDataSnapshot:()=>We,default:()=>xt,getAllApplications:()=>Qe,getPersonStatus:()=>Me,getQuestionStats:()=>ze,getQuotaStatus:()=>te,getRecentConnections:()=>Z,linkPersonToJob:()=>ke,loadApplicationQuestions:()=>me,loadConversations:()=>xe,loadJobs:()=>K,loadPeople:()=>w,loadQuota:()=>X,loadSyncState:()=>qe,logApplicationQuestions:()=>Ye,migrateFromDailyFiles:()=>Ue,recordConnectionSent:()=>Pe,resetCaches:()=>Ct,saveConversations:()=>Fe,saveJobs:()=>H,savePeople:()=>A,saveQuota:()=>de,saveSyncState:()=>Ve,updateConnectionStatus:()=>Je,upsertJob:()=>Le,upsertPerson:()=>fe});import"dotenv/config";import*as d from"fs";import*as m from"path";import{fileURLToPath as Ot}from"url";import{execSync as ce}from"child_process";function Ct(){x=null,F=null,I=null,T=null,L=null,R=null}function S(){d.existsSync(h)||d.mkdirSync(h,{recursive:!0})}function C(t,e){let o=JSON.stringify(e,null,2),s=t+".tmp";d.writeFileSync(s,o);for(let n=0;n<3;n++)try{d.renameSync(s,t);return}catch(r){if(n<2&&(r.code==="EPERM"||r.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw r}}function P(t,e){if(!d.existsSync(t))return null;try{let o=JSON.parse(d.readFileSync(t,"utf-8"));try{d.copyFileSync(t,t+".backup")}catch{}return o}catch(o){console.error(`[!] ${e} is corrupted: ${o.message}`),console.error(` File: ${m.resolve(t)}`);let s=t+".backup";if(d.existsSync(s))try{let n=JSON.parse(d.readFileSync(s,"utf-8"));console.error(` Restored from ${s}`);try{d.copyFileSync(s,t)}catch{}return n}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${t}.corrupted`);try{d.copyFileSync(t,t+".corrupted")}catch{}return null}}function k(){return new Date().toISOString().split("T")[0]}function w(){return x||(S(),x=P(le,"people.json")||{},x)}function A(t){S(),x=t;try{C(le,t)}catch(e){console.error(`[!] Failed to save people.json: ${e.message}`),console.error(` Path: ${m.resolve(le)}`)}}function K(){return F||(S(),F=P(ae,"jobs.json")||{},F)}function H(t){S(),F=t;try{C(ae,t)}catch(e){console.error(`[!] Failed to save jobs.json: ${e.message}`),console.error(` Path: ${m.resolve(ae)}`)}}function xe(){return T||(S(),T=P(G,"conversations.json")||{},At(T),T)}function At(t){let e=0;for(let o of Object.values(t)){if(!(o.draftStatus!==void 0||o.draftPreparedAt!==void 0))continue;let n=(o.messages||[]).find(r=>r.status==="draft");n&&o.draftStatus&&o.draftStatus!=="pending"&&(n.status=o.draftStatus),delete o.draftStatus,delete o.draftPreparedAt,e++}if(e>0){console.log(` [migration] Moved draft state from conv to msg.status for ${e} conversations`);try{C(G,t)}catch{}}}function Fe(t){S(),T=t;try{C(G,t)}catch(e){console.error(`[!] Failed to save conversations.json: ${e.message}`),console.error(` Path: ${m.resolve(G)}`)}}function fe(t,e={}){if(!t)return null;let o=w(),s=k(),n=new Date().toISOString(),r=o[t];if(r){r.seenCount=(r.seenCount||1)+1,r.lastSeen=s,r.lastUpdated=n,e.source&&!r.sources?.includes(e.source)&&(r.sources=r.sources||[],r.sources.push(e.source));for(let i of pe)e[i]!=null&&(r[i]=e[i])}else o[t]={name:e.name||null,title:e.title||null,location:e.location||null,company:e.company||null,connectionDegree:e.connectionDegree||null,connectionsCount:e.connectionsCount||null,mutualConnections:e.mutualConnections||null,photoUrl:e.photoUrl||null,hasVerifiedBadge:e.hasVerifiedBadge||!1,firstSeen:s,lastSeen:s,seenCount:1,sources:e.source?[e.source]:[],connectionStatus:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:n};return A(o),o[t]}function Le(t,e={}){if(!t)return null;let o=K(),s=k(),n=o[t];if(n){let r=["title","company","companyUrl","location","salary","url","applied","appliedDate","recruiterUrl","description","techStack","jobTags","seniority","employmentType","industries","jobFunction","postedTime","applicantCount","companySize","workplaceType","contractType","isEasyApply","applyType","scoutedAt","lastSeen"];for(let i of r)e[i]!=null&&(n[i]=e[i])}else o[t]={title:e.title||null,company:e.company||null,location:e.location||null,salary:e.salary||null,url:e.url||null,applied:e.applied||!1,appliedDate:e.appliedDate||s,recruiterUrl:e.recruiterUrl||null,firstSeen:s};return H(o),o[t]}function ke(t,e){if(!t||!e)return;let o=w(),s=o[t];s&&(s.jobIds||(s.jobIds=[]),s.jobIds.includes(e)||(s.jobIds.push(e),s.lastUpdated=new Date().toISOString(),A(o)))}function Ne(t,e){if(!t||!e)return;let o=w(),s=o[t];if(!s)return;s.encounters||(s.encounters=[]);let r={date:k(),source:e.source||"unknown",action:e.action||"unknown"};e.jobId&&(r.jobId=String(e.jobId)),e.jobTitle&&(r.jobTitle=e.jobTitle),e.jobCompany&&(r.jobCompany=e.jobCompany),!s.encounters.some(c=>c.date===r.date&&c.source===r.source&&c.action===r.action&&(c.jobId||"")===(r.jobId||""))&&(s.encounters.push(r),s.lastUpdated=new Date().toISOString(),A(o))}function Me(t){return t&&w()[t]?.connectionStatus||null}function Je(t,e,o={}){if(!t)return;let s=w(),n=s[t];if(n||(fe(t,o.profileInfo||{}),n=s[t]),!!n){if(n.connectionStatus=e,n.connectionDate=k(),n.connectionMethod=o.method||n.connectionMethod,n.messageSent=o.messageSent||n.messageSent,n.lastUpdated=new Date().toISOString(),o.profileInfo)for(let r of pe)o.profileInfo[r]!=null&&(n[r]=o.profileInfo[r]);A(s)}}function X(){return I||(S(),I=P(Ee,"quota.json"),(!I||!I.connections)&&(I={connections:[]}),I)}function de(t){S(),I=t;try{C(Ee,t)}catch(e){console.error(`[!] Failed to save quota.json: ${e.message}`)}}function Pe(t,e="unknown"){let o=X(),s=new Date;o.connections.push({date:s.toISOString().split("T")[0],time:s.toTimeString().split(" ")[0],profileUrl:t,source:e}),de(o)}function Z(){let t=X(),e=new Date;e.setDate(e.getDate()-7);let o=e.toISOString().split("T")[0];return t.connections.filter(s=>s.date>=o)}function ee(){let t=Z(),e=k(),o=t.filter(a=>a.date===e).length,s=t.length,n=o>=M,r=s>=J,i=!n&&!r,c={allowed:i,daily:{sent:o,max:M},weekly:{sent:s,max:J}};return i||(c.reason=n?`Daily limit reached (${o}/${M})`:`Weekly limit reached (${s}/${J})`),c}function te(){let t=Z(),e=k(),o=t.filter(n=>n.date===e).length,s=t.length;return{daily:{sent:o,max:M,remaining:M-o},weekly:{sent:s,max:J,remaining:J-s}}}function Et(t){if(!t)return null;let e=t.match(/jobs\/view\/(\d+)/);return e?e[1]:null}function Ue(){S();let t=d.readdirSync(h).filter(r=>r.endsWith(".json")&&r!=="people.json"&&r!=="jobs.json"&&r!=="quota.json");if(t.length===0)return console.log("No daily files found to migrate."),{people:0,jobs:0};console.log(`
2
+ Migrating from ${t.length} daily files...
3
+ `);let e=w(),o=K(),s=0,n=0;t.sort();for(let r of t){let i=m.join(h,r),c;try{c=JSON.parse(d.readFileSync(i,"utf-8"))}catch(u){console.log(` Skipping ${r}: ${u.message}`);continue}let a=c.date||r.match(/\d{4}-\d{2}-\d{2}/)?.[0]||null,f=c.source||r.split("-").slice(0,-1).join("-").replace(/-\d{4}$/,"")||"unknown";if(!c.profiles||!Array.isArray(c.profiles)){console.log(` Skipping ${r}: no profiles array`);continue}console.log(` ${r}: ${c.profiles.length} profiles`);for(let u of c.profiles){let v=u.profileUrl;if(!v)continue;let l=e[v];if(l){l.seenCount=(l.seenCount||1)+1,a&&(!l.lastSeen||a>l.lastSeen)&&(l.lastSeen=a),a&&(!l.firstSeen||a<l.firstSeen)&&(l.firstSeen=a),f&&!l.sources?.includes(f)&&(l.sources=l.sources||[],l.sources.push(f));for(let p of pe)u[p]!=null&&(l[p]=u[p]);u.status&&u.processed&&(l.connectionStatus=u.status,l.connectionDate=a,u.funnelResult&&(l.connectionMethod=u.funnelResult.method||l.connectionMethod,l.messageSent=u.funnelResult.message||l.messageSent)),l.lastUpdated=new Date().toISOString()}else e[v]={name:u.name||null,title:u.title||null,location:u.location||null,company:u.company||null,connectionDegree:u.connectionDegree||null,connectionsCount:u.connectionsCount||null,mutualConnections:u.mutualConnections||null,photoUrl:u.photoUrl||null,hasVerifiedBadge:u.hasVerifiedBadge||!1,firstSeen:a,lastSeen:a,seenCount:1,sources:f?[f]:[],connectionStatus:u.processed&&u.status||null,connectionDate:u.processed?a:null,connectionMethod:u.funnelResult?.method||null,messageSent:u.funnelResult?.message||null,jobIds:[],lastUpdated:new Date().toISOString()},s++;if(u.fromJob?.url){let p=Et(u.fromJob.url);p&&(e[v].jobIds||(e[v].jobIds=[]),e[v].jobIds.includes(p)||e[v].jobIds.push(p),o[p]?o[p].recruiterUrl||(o[p].recruiterUrl=v):(o[p]={title:u.fromJob.title||null,company:u.fromJob.company||null,location:null,salary:null,url:u.fromJob.url,applied:!0,appliedDate:a,recruiterUrl:v,firstSeen:a},n++))}}}return A(e),H(o),console.log(`
4
+ Migration complete!`),console.log(` People: ${Object.keys(e).length} total (${s} new)`),console.log(` Jobs: ${Object.keys(o).length} total (${n} new)`),{people:Object.keys(e).length,jobs:Object.keys(o).length}}function qe(){return L||(S(),L=P(_e,"sync-state.json")||{},L)}function Ve(t){S(),L=t;try{C(_e,t)}catch(e){console.error(`[!] Failed to save sync-state.json: ${e.message}`)}}function me(){if(R)return R;S();try{d.existsSync(ue)&&(R=JSON.parse(d.readFileSync(ue,"utf-8")))}catch{}return R||(R={}),R}function Be(t){return Array.isArray(t.applications)}function Qe(){let t=me();if(Be(t))return t.applications;let e=[];for(let o of Object.values(t))o.applications&&e.push(...o.applications);return e}function Ye(t){if(!t||!t.questions||t.questions.length===0)return;let e=me(),o=t.jobId||"unknown",n={applicationId:`${o}_${Date.now()}`,date:new Date().toISOString(),result:t.result||"submitted",formPages:t.formPages||null,duration:t.duration||null,retries:t.retries||0,failedField:t.failedField||null,questionCount:t.questions.length,questions:t.questions.map(r=>{let i={type:r.type||"input",question:r.labelText||r.question||"",answer:r.answer||""};return r.options&&(r.options.length<=_t?i.options=r.options:i.optionsOmitted=!0),r.page&&(i.page=r.page),r.wasPreFilled&&(i.wasPreFilled=!0),r.wasRetry&&(i.wasRetry=!0),i})};Be(e)?(n.jobId=o,n.jobTitle=t.jobTitle||null,n.jobCompany=t.jobCompany||null,n.jobUrl=t.jobUrl||null,e.applications.push(n)):(e[o]||(e[o]={applications:[]}),e[o].applications.push(n));try{C(ue,e)}catch(r){console.error(`[!] Failed to save application-questions.json: ${r.message}`)}}function ze(){let t=Qe(),e=new Map;for(let r of t)for(let i of r.questions||[]){let c=i.question.toLowerCase().trim();if(!c)continue;e.has(c)||e.set(c,{question:i.question,count:0,answers:new Map});let a=e.get(c);a.count++;let f=(i.answer||"").trim();a.answers.set(f,(a.answers.get(f)||0)+1)}let o=[...e.values()].sort((r,i)=>i.count-r.count),s=o.slice(0,15).map(r=>({question:r.question,count:r.count,topAnswer:[...r.answers.entries()].sort((i,c)=>c[1]-i[1])[0]?.[0]||""})),n=o.filter(r=>r.answers.size>1&&r.count>=3).map(r=>({question:r.question,count:r.count,answers:[...r.answers.entries()].sort((i,c)=>c[1]-i[1]).map(([i,c])=>`${i} (${c}x)`)}));return{totalApplications:t.length,uniqueQuestions:e.size,topQuestions:s,inconsistent:n}}function We(t){if(d.existsSync(m.join(h,".git")))try{let e={cwd:h,stdio:"pipe",timeout:1e4};ce("git add conversations.json people.json jobs.json quota.json sync-state.json run-log.json application-questions.json",e);try{ce("git diff --cached --quiet",e);return}catch{}let o=(t||`sync ${new Date().toISOString().slice(0,16)}`).replace(/"/g,'\\"');ce(`git commit -m "${o}"`,e)}catch(e){process.env.VERBOSE&&console.log(` [git] snapshot skipped: ${e.message?.slice(0,80)}`)}}var Rt,Tt,h,le,ae,Ee,G,_e,ue,M,J,x,F,I,T,L,R,pe,_t,xt,ge=De(()=>{Rt=Ot(import.meta.url),Tt=m.dirname(m.dirname(Rt)),h=process.env.DATA_DIR?m.resolve(process.env.DATA_DIR,"collected-profiles"):m.join(Tt,"collected-profiles"),le=m.join(h,"people.json"),ae=m.join(h,"jobs.json"),Ee=m.join(h,"quota.json"),G=m.join(h,"conversations.json"),_e=m.join(h,"sync-state.json"),ue=m.join(h,"application-questions.json"),M=20,J=100,x=null,F=null,I=null,T=null,L=null,R=null;pe=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge"];_t=50;xt={loadPeople:w,savePeople:A,loadJobs:K,saveJobs:H,loadConversations:xe,saveConversations:Fe,upsertPerson:fe,upsertJob:Le,linkPersonToJob:ke,addEncounter:Ne,getPersonStatus:Me,updateConnectionStatus:Je,migrateFromDailyFiles:Ue,loadQuota:X,saveQuota:de,recordConnectionSent:Pe,canSendConnection:ee,getRecentConnections:Z,getQuotaStatus:te,loadSyncState:qe,saveSyncState:Ve,commitDataSnapshot:We,logApplicationQuestions:Ye,getQuestionStats:ze}});var ot={};Ie(ot,{getJobDecision:()=>Pt,getLastVisit:()=>Bt,getScoutStats:()=>Qt,loadCompanies:()=>tt,loadDiscoveredPeople:()=>ye,loadJobDecisions:()=>Se,loadScoutedJobs:()=>he,loadVisitLog:()=>oe,recordVisit:()=>Vt,resetCaches:()=>Nt,saveCompany:()=>qt,saveDiscoveredPerson:()=>Ut,saveScoutedJob:()=>Mt,setJobDecision:()=>Jt});import"dotenv/config";import*as D from"fs";import*as y from"path";import{fileURLToPath as Ft}from"url";function Q(){D.existsSync(_)||D.mkdirSync(_,{recursive:!0})}function Y(t,e){let o=JSON.stringify(e,null,2),s=t+".tmp";D.writeFileSync(s,o);for(let n=0;n<3;n++)try{D.renameSync(s,t);return}catch(r){if(n<2&&(r.code==="EPERM"||r.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw r}}function z(t){try{if(D.existsSync(t))return JSON.parse(D.readFileSync(t,"utf-8"))}catch{}return null}function Nt(){U=null,q=null,V=null,B=null,E=null}function he(){return U||(U=z(Ke)||{},U)}function Mt(t,e){if(!t)return;Q();let o=he(),s=new Date().toISOString().slice(0,10);if(o[t]){for(let[n,r]of Object.entries(e))r!=null&&(o[t][n]=r);o[t].lastSeen=s}else o[t]={...e,scoutedAt:s,lastSeen:s};try{Y(Ke,o)}catch(n){console.error(`[!] Failed to save scouted-jobs.json: ${n.message}`)}}function Se(){return q||(q=z(et)||{},q)}function Jt(t,e,o={}){if(!t||!e)return;Q();let s=Se(),n=new Date().toISOString().slice(0,10);s[t]?(s[t].status=e,s[t].updatedAt=n,o.reason&&(s[t].reason=o.reason),o.score!=null&&(s[t].score=o.score),o.source&&(s[t].source=o.source)):s[t]={status:e,decidedAt:n,updatedAt:n,reason:o.reason||null,score:o.score??null,source:o.source||null};try{Y(et,s)}catch(r){console.error(`[!] Failed to save job-decisions.json: ${r.message}`)}}function Pt(t){return Se()[t]||null}function ye(){return V||(V=z(He)||{},V)}function Ut(t,e){if(!t)return;Q();let o=ye(),s=new Date().toISOString().slice(0,10);if(o[t]){let n=o[t];e.name&&(n.name=e.name),e.title&&(n.title=e.title),e.company&&(n.company=e.company),e.location&&(n.location=e.location),e.role&&(n.role=e.role),e.isConnected!=null&&(n.isConnected=e.isConnected),e.jobId&&(n.jobIds||(n.jobIds=[]),n.jobIds.includes(e.jobId)||n.jobIds.push(e.jobId)),n.lastSeen=s,n.seenCount=(n.seenCount||1)+1}else o[t]={name:e.name||null,title:e.title||null,company:e.company||null,location:e.location||null,role:e.role||null,source:e.source||"scout",isConnected:e.isConnected||!1,discoveredAt:s,lastSeen:s,seenCount:1,jobIds:e.jobId?[e.jobId]:[]};try{Y(He,o)}catch(n){console.error(`[!] Failed to save discovered-people.json: ${n.message}`)}}function tt(){return B||(B=z(Ze)||{},B)}function qt(t,e){if(!t)return;Q();let o=tt(),s=new Date().toISOString().slice(0,10);if(o[t]){let n=o[t];e.name&&(n.name=e.name),e.about&&(n.about=e.about),e.size&&(n.size=e.size),e.industries&&(n.industries=e.industries),e.jobId&&(n.jobIds||(n.jobIds=[]),n.jobIds.includes(e.jobId)||n.jobIds.push(e.jobId)),e.recruiterUrl&&(n.recruiterUrls||(n.recruiterUrls=[]),n.recruiterUrls.includes(e.recruiterUrl)||n.recruiterUrls.push(e.recruiterUrl)),n.lastSeen=s,n.jobCount=n.jobIds?.length||0}else o[t]={name:e.name||null,about:e.about||null,size:e.size||null,industries:e.industries||null,discoveredAt:s,lastSeen:s,jobIds:e.jobId?[e.jobId]:[],recruiterUrls:e.recruiterUrl?[e.recruiterUrl]:[],jobCount:e.jobId?1:0};try{Y(Ze,o)}catch(n){console.error(`[!] Failed to save companies.json: ${n.message}`)}}function oe(){return E||(E=z(Xe)||{visits:[]},Array.isArray(E.visits)||(E.visits=[]),E)}function Vt(t,e={}){if(!t)return;Q();let o=oe();o.visits.push({date:new Date().toISOString(),profileUrl:t,name:e.name||null,source:e.source||"manual"});let s=Date.now()-90*24*60*60*1e3;o.visits=o.visits.filter(n=>new Date(n.date).getTime()>s);try{Y(Xe,o)}catch(n){console.error(`[!] Failed to save visit-log.json: ${n.message}`)}}function Bt(t){let e=oe();for(let o=e.visits.length-1;o>=0;o--)if(e.visits[o].profileUrl===t)return e.visits[o].date;return null}function Qt(){let t=he(),e=ye(),o=Object.values(t),s=Object.values(e),n={};for(let l of o)l.company&&(n[l.company]=(n[l.company]||0)+1);let r=Object.entries(n).sort((l,p)=>p[1]-l[1]).slice(0,5).map(([l,p])=>`${l} (${p})`),i={};for(let l of o)for(let p of l.techStack||[])i[p]=(i[p]||0)+1;let c=Object.entries(i).sort((l,p)=>p[1]-l[1]).slice(0,8).map(([l,p])=>`${l} (${p})`),a=oe(),f=new Date().toISOString().slice(0,10),u=a.visits.filter(l=>l.date.startsWith(f)).length,v=new Set(a.visits.map(l=>l.profileUrl)).size;return{totalJobs:o.length,totalRecruiters:s.length,topCompanies:r,topTech:c,totalVisited:v,todayVisited:u,lastScoutDate:o.length>0?o.sort((l,p)=>(p.scoutedAt||"").localeCompare(l.scoutedAt||""))[0]?.scoutedAt:null}}var Lt,kt,_,Ke,He,Xe,Ze,et,U,q,V,B,E,nt=De(()=>{Lt=Ft(import.meta.url),kt=y.dirname(y.dirname(Lt)),_=process.env.DATA_DIR?y.resolve(process.env.DATA_DIR,"market-research"):y.join(kt,"market-research"),Ke=y.join(_,"scouted-jobs.json"),He=y.join(_,"discovered-people.json"),Xe=y.join(_,"visit-log.json"),Ze=y.join(_,"companies.json"),et=y.join(_,"job-decisions.json");U=null;q=null;V=null;B=null;E=null});import{execFileSync as Ht}from"child_process";import"dotenv/config";import*as b from"fs";import*as $ from"path";import{fileURLToPath as Dt}from"url";import"dotenv/config";import{createGraphClient as St,resolveToken as yt}from"@ilivemylife/graph-sdk";import{config as jt}from"dotenv";jt();var $e=yt()||process.env.TOKEN;$e||(console.error(`
5
+ [FATAL] No iLiveMyLife token found.`),console.error(" The bot uses Lifebot AI to decide which jobs to apply for"),console.error(` and how to fill application forms. It cannot work without it.
6
+ `),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
7
+ `),console.error(` Or add ILML_TOKEN=... to .env
8
+ `),process.exit(1));var we=St({token:$e});function vt(t){if(!t||t<1e3)return`${t||0}ms`;let e=Math.round(t/1e3);if(e<60)return`${e}s`;let o=Math.floor(e/60),s=e%60;return s?`${o}m ${s}s`:`${o}m`}function bt({scriptName:t,mode:e,status:o,error:s,duration:n}){let r=o==="error"?"\u2717":o==="quota-reached"?"\u26A0":"\u2713",i=new Date().toISOString().slice(0,16).replace("T"," "),c=n?` ${vt(n)}`:"",a=o==="error"?`error${s?`: ${String(s).slice(0,80)}`:""}`:o==="quota-reached"?"daily quota reached":"done",f=e&&e!=="default"?` (${e})`:"";return`${r} ${t}${f} \u2014 ${a} \u2014 ${i}${c}`}async function Oe({scriptName:t,mode:e,status:o,summaryLines:s,error:n,duration:r}){let i=process.env.NODE_RUN_REPORTS;if(!i||!Array.isArray(s)||s.length===0)return;let a=`**${bt({scriptName:t,mode:e,status:o,error:n,duration:r})}**
9
+
10
+ ${s.join(`
11
+ `)}`;try{await we.addMessage(i,a)}catch(f){console.error(`[runReport] Failed to post end-of-session message: ${f.message}`)}}var It=Dt(import.meta.url),$t=$.dirname($.dirname(It)),re=process.env.DATA_DIR?$.resolve(process.env.DATA_DIR,"collected-profiles"):$.join($t,"collected-profiles"),ie=$.join(re,"run-log.json");function wt(t,e){let o=JSON.stringify(e,null,2),s=t+".tmp";b.writeFileSync(s,o);for(let n=0;n<3;n++)try{b.renameSync(s,t);return}catch(r){if(n<2&&(r.code==="EPERM"||r.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw r}}function Re(){try{if(!b.existsSync(ie))return{runs:[]};let t=JSON.parse(b.readFileSync(ie,"utf-8"));return!t.runs||!Array.isArray(t.runs)?{runs:[]}:t}catch{return{runs:[]}}}function Te(t){b.existsSync(re)||b.mkdirSync(re,{recursive:!0});try{wt(ie,t)}catch(e){console.error(`[!] Failed to save run-log.json: ${e.message}`)}}function Ce(t,e="default"){let o=Re();for(let i of o.runs)i.status==="running"&&(i.status="interrupted",i.result="interrupted",i.timestamp&&(i.duration=Date.now()-new Date(i.timestamp).getTime()));let s=new Date,n={timestamp:s.toISOString(),script:t,mode:e,status:"running",result:null,duration:null,stats:null,error:null};o.runs.push(n);let r=Date.now()-30*24*60*60*1e3;return o.runs=o.runs.filter(i=>new Date(i.timestamp).getTime()>r),Te(o),{script:t,mode:e,startTime:s.getTime(),runIndex:o.runs.length-1}}function Ae(t,e,o=null,s=null){if(!t)return;let n=null;try{let i=Re(),c=i.runs[t.runIndex];if(c&&c.status==="running"&&c.script===t.script)c.status=e,c.result=e,c.duration=Date.now()-t.startTime,c.stats=o,c.error=s,n=c.duration;else{let a=i.runs.find(f=>f.script===t.script&&f.status==="running"&&f.timestamp===new Date(t.startTime).toISOString());a&&(a.status=e,a.result=e,a.duration=Date.now()-t.startTime,a.stats=o,a.error=s,n=a.duration)}Te(i)}catch(i){console.error(`[runLog] Failed to end run: ${i.message}`)}let r=o&&Array.isArray(o.summaryLines)?o.summaryLines:null;if(r&&r.length>0&&process.env.NODE_RUN_REPORTS){let i=e==="error"?"error":o&&o.dailyLimitHit?"quota-reached":"done";Oe({scriptName:t.script,mode:t.mode,status:i,summaryLines:r,error:s,duration:n??Date.now()-t.startTime}).catch(c=>console.error(`[runLog] postRunReport failed: ${c.message}`))}}ge();import"dotenv/config";import*as j from"fs";import*as g from"path";import{fileURLToPath as Yt}from"url";var zt=Yt(import.meta.url),Wt=g.dirname(zt),it=g.dirname(g.dirname(Wt)),ne=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(it,"collected-profiles"),se=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"market-research"):g.join(it,"market-research"),je=1,Gt=[{version:1,file:"./v001-normalize-jobs-and-questions.mjs"}];function ct(t){return g.join(t,".schema-version")}function ve(t){let e=ct(t);if(!j.existsSync(e))return{version:0,migrations:[]};try{return JSON.parse(j.readFileSync(e,"utf-8"))}catch{return{version:0,migrations:[]}}}function Kt(t,e){j.writeFileSync(ct(t),JSON.stringify(e,null,2))}function st(t,e){if(!j.existsSync(t))return;let o=`.pre-v${String(e).padStart(3,"0")}.backup`,s=j.readdirSync(t).filter(n=>n.endsWith(".json")&&!n.includes(".backup"));for(let n of s){let r=g.join(t,n),i=g.join(t,n+o);try{j.copyFileSync(r,i)}catch(c){console.error(` [!] Failed to backup ${n}: ${c.message}`)}}s.length>0&&console.log(` Backed up ${s.length} files in ${g.basename(t)}/ (${o})`)}var rt=!1;async function lt(){if(rt)return;rt=!0;let t=ve(ne),e=ve(se),o=Math.min(t.version,e.version);if(o>=je)return;console.log(`
12
+ \u{1F4E6} Schema migration: v${o} \u2192 v${je}`);let s=Gt.filter(n=>n.version>o);for(let n of s){console.log(`
13
+ Running migration v${String(n.version).padStart(3,"0")}...`),st(ne,n.version),st(se,n.version);let r=await import(n.file);try{await r.up(ne,se)}catch(a){console.error(`
14
+ [FATAL] Migration v${n.version} failed: ${a.message}`),console.error(` Backups are in collected-profiles/ and market-research/ (.pre-v${String(n.version).padStart(3,"0")}.backup)`),console.error(` Fix the issue and restart.
15
+ `),process.exit(1)}let i=new Date().toISOString(),c={version:n.version,name:r.NAME||`v${String(n.version).padStart(3,"0")}`,appliedAt:i};for(let a of[ne,se]){if(!j.existsSync(a))continue;let f=ve(a);f.version=n.version,f.migratedAt=i,f.migrations.push(c),Kt(a,f)}console.log(` \u2713 Migration v${String(n.version).padStart(3,"0")} complete`)}try{let{resetCaches:n}=await Promise.resolve().then(()=>(ge(),Ge));n()}catch{}try{let{resetCaches:n}=await Promise.resolve().then(()=>(nt(),ot));n()}catch{}console.log(`
16
+ \u2713 Schema is now at v${je}
17
+ `)}await lt();var W=process.argv.slice(2),be=new Set((W.find(t=>t.startsWith("--skip="))?.split("=")[1]||"").split(",").filter(Boolean)),at=new Set((W.find(t=>t.startsWith("--only="))?.split("=")[1]||"").split(",").filter(Boolean)),ft=W.includes("--dry-run"),dt=W.includes("--jobs"),mt=W.includes("--comms"),ut=["apply-queue","apply","funnel","scout","visit"],pt=["sync","today"],gt=dt?ut:mt?pt:[...ut,...pt];function O(t){return at.size>0?at.has(t):!be.has(t)&&gt.includes(t)}function N(t,e,o=[],{optional:s=!1}={}){if(console.log(`
18
+ ${"\u2550".repeat(50)}`),console.log(` STEP: ${t}`),console.log("\u2550".repeat(50)),ft)return console.log(` [DRY RUN] Would run: node ${e} ${o.join(" ")}`),!0;try{return Ht("node",[e,...o],{stdio:"inherit",timeout:18e6}),console.log(` \u2713 ${t} completed`),!0}catch(n){return s?(console.log(` \u26A0 ${t} failed (optional): ${n.message?.slice(0,80)}`),!1):(console.error(` \u2717 ${t} failed: ${n.message?.slice(0,80)}`),!1)}}async function Xt(){let t=dt?"jobs":mt?"comms":"full";console.log(`
19
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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(` DAILY PIPELINE (${t})`),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");let e=gt.filter(O);console.log(` Steps: ${e.join(" \u2192 ")}`),be.size>0&&console.log(` Skipping: ${[...be].join(", ")}`),ft&&console.log(" [DRY RUN MODE]");let o=Ce("daily",t),s={};if(O("apply-queue")&&(s["apply-queue"]=N("Apply Queue (scored jobs)","applyQueue.mjs",["--max=50","--min-score=50"],{optional:!0})),O("apply")){let r=te();s.apply=N("Apply from Search","run.mjs",["--no-wait"],{optional:!0})}if(O("funnel")&&!O("apply")){let r=ee();r.allowed?s.funnel=N("Connection Funnel","funnelRunner.mjs",["--max=20"],{optional:!0}):(console.log(`
20
+ \u26A0 Funnel skipped: ${r.reason}`),s.funnel="skipped_quota")}O("scout")&&(s.scout=N("Scout Jobs for Tomorrow","scout.mjs",["--max=300","--verbose"],{optional:!0})),O("sync")&&(s.sync=N("Sync Inbox","syncAll.mjs")),O("today")&&(s.today=N("Daily Report","syncAll.mjs",["--today"])),console.log(`
21
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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(" PIPELINE COMPLETE"),console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");for(let[r,i]of Object.entries(s))console.log(` ${i===!0?"\u2713":i===!1?"\u2717":"\u26A0"} ${r}: ${i===!0?"success":i===!1?"failed":i}`);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
22
+ `);let n=Object.values(s).some(r=>r===!1);Ae(o,n?"partial":"success",s)}Xt().catch(t=>{console.error("Daily pipeline failed:",t),process.exit(1)});