ilml-plugin-linkedin 1.12.0 → 1.13.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/ilml-plugin.json CHANGED
@@ -1,35 +1,175 @@
1
1
  {
2
2
  "name": "linkedin",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data with AI-assisted workflows",
5
5
  "commands": {
6
- "help": { "run": "node dist/help.mjs", "description": "List all commands with descriptions; or `help <cmd>` for detailed info on one command (usage, flags, ban-risk, when-to-use). Reads ilml-plugin.json directly." },
7
- "apply": { "run": "node dist/run.mjs", "description": "Auto-apply to LinkedIn Easy Apply jobs from LINKEDIN_SEARCH_URL. Stops after MAX_APPLY_FOR_RUN. Browser flow.", "banRisk": "Medium. Each apply submission is a purposeful action; LinkedIn doesn't flag this as long as you respect MAX_APPLY_FOR_RUN (default 50/day)." },
8
- "sync": { "run": "node dist/syncAll.mjs", "description": "Alias for sync-all." },
9
- "sync-all": { "run": "node dist/syncAll.mjs", "description": "Pull LinkedIn inbox into local DB + graph. Preserves unread badges by default. Flags: --full (re-scan), --report (stats-only no browser), --read-unread (vacuums unread threads — loses badges)." },
10
- "today": { "run": "node dist/syncAll.mjs --today", "description": "No-browser daily action plan: who to reply to, what to do, prioritised. Reads already-synced data." },
11
- "enrich": { "run": "node dist/syncAll.mjs --enrich", "description": "Re-classify every conversation (recruiter / HM / founder / etc.) and rebuild summaries, priorities, suggested actions. Offline, no browser." },
12
- "report": { "run": "node dist/syncAll.mjs --report", "description": "Print the summary of the latest session. Offline." },
13
- "scout": { "run": "node dist/scout.mjs", "description": "Scan jobs from LINKEDIN_SCOUT_URLS and score them — WITHOUT applying. Builds queue for apply-queue. Browser flow." },
14
- "messages": { "run": "node dist/messageBot.mjs", "description": "Batch messaging: draft → review → push. Nothing sends without explicit approval. Common flags: --review-drafts (interactive), --push-drafts (send approved)." },
15
- "funnel": { "run": "node dist/funnelRunner.mjs", "description": "Recruiter / founder / investor connection-request queue. Browser flow.", "banRisk": "Medium. Each connection request is purposeful. Capped by FUNNEL_MAX_CONNECTIONS / weekly quota." },
16
- "visit": { "run": "node dist/visit.mjs", "description": "Visit recruiter / target profiles so they see you in 'who viewed your profile'. Browser flow.", "banRisk": "Medium. Visits are purposeful (show-up signal). Up to 50/session by default." },
17
- "viewers": { "run": "node dist/viewers.mjs", "description": "Pull the 'who viewed your profile' list from LinkedIn analytics. Browser flow." },
18
- "apply-queue": { "run": "node dist/applyQueue.mjs", "description": "Apply to top-scored jobs from the latest scout run. Browser flow." },
19
- "warm-scan": { "run": "node dist/warmScan.mjs", "description": "Find 1st-degree connections at target companies. Offline, no browser. Flags: --companies=\"X,Y\" or --group=<named-group>." },
20
- "daily": { "run": "node dist/daily.mjs", "description": "Full pipeline: sync-all → apply → scout → funnel → visit → viewers → report. Browser flow." },
21
- "profile-history": { "run": "node dist/profileHistory.mjs", "description": "Inspect saved snapshots of profile changes over time. Offline." },
22
- "verify-about": { "run": "node dist/verifyAbout.mjs", "description": "Smoke test for the About-field selector — read-only diagnostic (visits own profile, prints what was scraped). Doesn't write to disk." },
6
+ "help": { "run": "node dist/help.mjs", "description": "List all commands with descriptions; or `help <cmd>` for detailed info on one command (usage, flags, ban-risk, when-to-use). Reads ilml-plugin.json directly.", "usage": "ilml linkedin help [<cmd>]", "useWhen": "You forgot what a command does, or you're an AI agent picking the right tool." },
7
+ "apply": {
8
+ "run": "node dist/run.mjs",
9
+ "description": "Auto-apply to LinkedIn Easy Apply jobs from your saved search URL. Visits each job, fills the multi-step form using Lifebot AI (form Q&A), submits, moves on. Stops at MAX_APPLY_FOR_RUN.",
10
+ "usage": "ilml linkedin apply",
11
+ "flags": {
12
+ "(no flags — driven by config)": "Reads LINKEDIN_SEARCH_URL (required) and MAX_APPLY_FOR_RUN (default 50). Set both via `ilml plugin config linkedin set`."
13
+ },
14
+ "banRisk": "Medium. Each apply is a purposeful action LinkedIn sees as normal. Respect MAX_APPLY_FOR_RUN — exceeding ~100/day flags accounts.",
15
+ "useWhen": "You have an active job search and want the plugin to drain your daily Easy-Apply queue."
16
+ },
17
+ "sync": { "run": "node dist/syncAll.mjs", "description": "Alias for sync-all.", "usage": "ilml linkedin sync [flags…]" },
18
+ "sync-all": {
19
+ "run": "node dist/syncAll.mjs",
20
+ "description": "Pull your LinkedIn inbox into the local DB. Phase 1 scans the sidebar for new/changed threads, Phase 2 opens each changed thread and saves its messages, Phase 3 enriches (categorise, summarise, suggest action), Phase 4 prints a report. Preserves LinkedIn's bold 'unread' badges by default. Saves to people.json + conversations.json. Atomic writes throughout — safe to Ctrl-C.",
21
+ "usage": "ilml linkedin sync-all [--full] [--max=N] [--report] [--read-unread] [--auto-enrich-replies] [--auto-enrich-max=N] [--auto-enrich-stale-days=N] [--dry-run] [--verbose]",
22
+ "flags": {
23
+ "--full": "Scan the entire inbox (not incremental). Slow but catches drift.",
24
+ "--max=N": "Cap how many threads Phase 2 opens (default 50). Lower = faster sync, but pending threads stay queued.",
25
+ "--report": "Print latest sync stats and exit. No browser. Safe.",
26
+ "--read-unread": "Open unread threads — loses LinkedIn's bold-badge state. Default: list them in the report but don't open.",
27
+ "--auto-enrich-replies": "OPT-IN. After Phase 2, deep-read the LinkedIn profile of contacts who just wrote to you (Experience/Education/Skills/Recommendations/Certifications). Capped by --auto-enrich-max. Default OFF.",
28
+ "--auto-enrich-max=N": "Cap for --auto-enrich-replies (default 3). Each enriched profile = ~6-7 page loads.",
29
+ "--auto-enrich-stale-days=N": "Skip-if-fresh threshold for --auto-enrich-replies (default 30 days). Recently-enriched contacts are skipped.",
30
+ "--dry-run": "Don't write to disk or graph. Combine with --auto-enrich-replies to preview what would be enriched.",
31
+ "--verbose": "Per-thread logging."
32
+ },
33
+ "banRisk": "Low for the base sync (inbox scan + opening threads = normal). Medium when --auto-enrich-replies is on, because each enriched profile adds ~6-7 page loads on a contact you're not actively messaging in the same session.",
34
+ "useWhen": "Daily — typically once per morning. Combine with `today` to get your prioritised reply list."
35
+ },
36
+ "today": { "run": "node dist/syncAll.mjs --today", "description": "No-browser daily action plan from already-synced data: who to reply to, what to do, prioritised by category and last activity.", "usage": "ilml linkedin today", "useWhen": "First thing each morning — what should I touch today?" },
37
+ "enrich": { "run": "node dist/syncAll.mjs --enrich", "description": "Re-classify every saved conversation (recruiter / HM / founder / coach / etc.) and rebuild summaries, priorities, suggested actions. Offline, no browser. Idempotent.", "usage": "ilml linkedin enrich", "useWhen": "After upgrading the plugin and enrichment rules changed, or to refresh stale auto-tags." },
38
+ "report": { "run": "node dist/syncAll.mjs --report", "description": "Print the summary of the latest sync session and current inbox stats. No browser, no writes.", "usage": "ilml linkedin report", "useWhen": "Quick health check on the local DB and recent sync results." },
39
+ "scout": {
40
+ "run": "node dist/scout.mjs",
41
+ "description": "Scan jobs from LINKEDIN_SCOUT_URLS (multiple search URLs supported), score them, and save to market-research/ — WITHOUT applying. Builds a ranked queue for apply-queue to consume.",
42
+ "usage": "ilml linkedin scout [--max=N] [--verbose] [--list] [--url=<search-url>]",
43
+ "flags": {
44
+ "--max=N": "Cap jobs scouted this run (default 200).",
45
+ "--verbose": "Per-job scoring detail.",
46
+ "--list": "Show scout stats only, no browser. Safe.",
47
+ "--url=<search-url>": "One-off custom search URL (overrides LINKEDIN_SCOUT_URLS for this run)."
48
+ },
49
+ "banRisk": "Low. Browsing job listings is normal. Cap is conservative.",
50
+ "useWhen": "You want broad market intel before deciding what to apply to, or to seed apply-queue."
51
+ },
52
+ "messages": {
53
+ "run": "node dist/messageBot.mjs",
54
+ "description": "Batch messaging workflow: draft replies (via JSON file from your AI assistant) → review → push. Nothing sends without explicit approval. Drafts live as `status: 'draft'` inside conversations.json; push-drafts re-syncs each thread before sending and aborts if a new message arrived in the meantime.",
55
+ "usage": "ilml linkedin messages [--list] [--review-drafts] [--push-drafts] [--draft 'Name' 'text'] [--draft-batch @file.json] [--send 'Name' 'text'] [--send-batch @file.json] [--review 'Name'] [--review-batch @names.json] [--follow-up 'Name' 'note' '2026-04-15']",
56
+ "flags": {
57
+ "--list": "Show inbox conversation stats. No browser.",
58
+ "--review-drafts": "Interactive CLI to approve/edit/reject pending drafts. No browser.",
59
+ "--push-drafts": "Send all confirmed drafts in one browser session. Per-thread safety re-check.",
60
+ "--draft 'Name' 'text'": "Save ONE reply as a draft on Name's conversation. No browser, no send.",
61
+ "--draft-batch @file.json": "Save MANY drafts from a JSON array [{name,text}, ...]. AI-friendly entry point.",
62
+ "--send 'Name' 'text'": "Send immediately to Name (bypasses draft workflow). Salutation guard applies.",
63
+ "--send-batch @file.json": "Send many. Each goes through the salutation guard.",
64
+ "--review 'Name'": "Mark Name's conversation as reviewed (clears 'unreviewed' status). No browser.",
65
+ "--review-batch @names.json": "Mark many as reviewed.",
66
+ "--follow-up 'Name' 'note' 'date'": "Set a follow-up reminder on Name's conversation.",
67
+ "--read 'Name'": "Re-read Name's thread (one-off, browser)."
68
+ },
69
+ "banRisk": "Low. All sends go through the salutation guard (refuses 'Hi Dan' under Alexey's thread) and pre-send freshness check. Cap is your willingness to push.",
70
+ "useWhen": "Your AI assistant has prepared a JSON of replies — feed it via --draft-batch, eyeball with --review-drafts, ship with --push-drafts."
71
+ },
72
+ "funnel": {
73
+ "run": "node dist/funnelRunner.mjs",
74
+ "description": "Recruiter / founder / investor connection-request queue. Reads people.json + discovered-people.json, picks profiles matching FUNNEL_* config, sends connection requests (with or without custom note depending on config), records each in quota.json.",
75
+ "usage": "ilml linkedin funnel [--list] [--max=N] [--verbose]",
76
+ "flags": {
77
+ "--list": "Show queue stats only, no browser. Safe.",
78
+ "--max=N": "Cap connection requests this run (default = FUNNEL_MAX_CONNECTIONS or 20).",
79
+ "--verbose": "Per-profile reasoning."
80
+ },
81
+ "banRisk": "Medium. Connection requests are purposeful but rate-limited by LinkedIn (~100/week free, ~200/week premium). Default cap and weekly quota tracker keep you under.",
82
+ "useWhen": "You want to grow targeted reach (recruiters at companies you'd apply to, founders in your domain)."
83
+ },
84
+ "visit": {
85
+ "run": "node dist/visit.mjs",
86
+ "description": "Visit a queue of profiles so you appear in their 'who viewed your profile' list. Source-aware: pulls candidates from people.json, discovered-people.json, or job recruiters.",
87
+ "usage": "ilml linkedin visit [--source=<source>] [--max=N] [--list]",
88
+ "flags": {
89
+ "--source=<source>": "Which queue to drain. 'people' (default), 'recruiters', 'discovered'.",
90
+ "--max=N": "Cap visits this run (default 50).",
91
+ "--list": "Stats only, no browser. Safe."
92
+ },
93
+ "banRisk": "Medium. Profile views are public-by-default LinkedIn signal — visits to dozens of recruiters can be flagged if cadence is unnatural. Cap = 50/session.",
94
+ "useWhen": "You want recruiters at target companies to notice you without sending a connection request."
95
+ },
96
+ "viewers": {
97
+ "run": "node dist/viewers.mjs",
98
+ "description": "Pull the 'who viewed your profile' list from LinkedIn analytics and save to market-research/profile-viewers.json. The default `--save` mode actually persists; pass --list for stats only.",
99
+ "usage": "ilml linkedin viewers [--list]",
100
+ "flags": {
101
+ "--list": "Viewer stats only, no browser, no save."
102
+ },
103
+ "banRisk": "Low. One analytics page view.",
104
+ "useWhen": "Weekly check on who saw you (Premium accounts see names, free accounts see snippets)."
105
+ },
106
+ "apply-queue": {
107
+ "run": "node dist/applyQueue.mjs",
108
+ "description": "Apply to top-scored jobs from the latest scout run. Pulls from market-research/scouted-jobs.json, filters by score and Easy-Apply availability, applies one by one.",
109
+ "usage": "ilml linkedin apply-queue [--max=N] [--list] [--min-score=N]",
110
+ "flags": {
111
+ "--max=N": "Cap applications this run.",
112
+ "--list": "Show the scored queue, no browser. Safe.",
113
+ "--min-score=N": "Skip jobs scored below N (default tuned for your config)."
114
+ },
115
+ "banRisk": "Medium. Same as `apply` — purposeful but volume-sensitive.",
116
+ "useWhen": "After `scout`, when you have a curated queue to drain."
117
+ },
118
+ "warm-scan": {
119
+ "run": "node dist/warmScan.mjs",
120
+ "description": "Find your 1st-degree connections at target companies. Offline read of people.json. Use to identify mutual paths into a company before cold outreach.",
121
+ "usage": "ilml linkedin warm-scan [--companies=\"X,Y,Z\"] [--group=<named-group>]",
122
+ "flags": {
123
+ "--companies=\"X,Y,Z\"": "Comma-separated company-name filter.",
124
+ "--group=<name>": "Use a named group from your config (e.g., 'frontier-ai-labs')."
125
+ },
126
+ "banRisk": "None — offline.",
127
+ "useWhen": "Before drafting an outreach to a company — see who you already know there."
128
+ },
129
+ "daily": {
130
+ "run": "node dist/daily.mjs",
131
+ "description": "Run the full pipeline in one go: sync-all → apply → scout → funnel → visit → viewers → report. Each phase respects its own config caps. Sequential to keep ban-risk natural.",
132
+ "usage": "ilml linkedin daily [--jobs] [--comms]",
133
+ "flags": {
134
+ "--jobs": "Only the job-side phases (apply, scout, apply-queue).",
135
+ "--comms": "Only the communication-side phases (sync-all, today)."
136
+ },
137
+ "banRisk": "Sum of all enabled phases. Don't run more than once per day.",
138
+ "useWhen": "End-of-day cron: 'do everything LinkedIn for me, then send report'."
139
+ },
140
+ "profile-history": {
141
+ "run": "node dist/profileHistory.mjs",
142
+ "description": "Inspect saved snapshots of profile changes over time. People you've visited multiple times have a snapshot history — see what changed (title moved jobs, location updated, About rewritten).",
143
+ "usage": "ilml linkedin profile-history [<profile-url>]",
144
+ "flags": {
145
+ "<profile-url>": "Optional. If given, show history for one person. If omitted, list all profiles with multiple snapshots."
146
+ },
147
+ "banRisk": "None — offline.",
148
+ "useWhen": "Before reaching out — see if someone has changed roles or companies since you last interacted."
149
+ },
150
+ "verify-about": {
151
+ "run": "node dist/verifyAbout.mjs",
152
+ "description": "Smoke test for the About-field selector. Visits your own profile, prints what was scraped — basics only. Doesn't write to disk.",
153
+ "usage": "ilml linkedin verify-about",
154
+ "banRisk": "Low — one profile view (your own).",
155
+ "useWhen": "After a LinkedIn DOM change you suspect broke parsing. For full profile-audit use `enrich-profile <your-url> --full` instead."
156
+ },
23
157
  "enrich-profile": {
24
158
  "run": "node dist/enrichProfile.mjs",
25
- "description": "Visit ONE LinkedIn profile by URL and save what we learn to people.json (the contacts DB). Self-profile is detected via MY_LINKEDIN_URL / LINKEDIN_NAME and printed to stdout instead of saved.",
26
- "usage": "ilml linkedin enrich-profile <profile-url> [--with-experience] [--help]",
159
+ "description": "Visit ONE LinkedIn profile by URL and save what we learn to people.json (the contacts DB). With --full (or selected --with-* flags) also fetches /details/<section>/ subpages for full structured Experience / Featured / Education / Skills / Recommendations. Self-profile detected via MY_LINKEDIN_URL / LINKEDIN_NAME → printed to stdout instead of saved.",
160
+ "usage": "ilml linkedin enrich-profile <profile-url> [--full | --with-experience --with-featured --with-education --with-skills --with-recommendations --with-certifications] [--help]",
27
161
  "flags": {
28
- "<profile-url>": "Required. Full LinkedIn profile URL, e.g. https://www.linkedin.com/in/<slug>/",
29
- "--with-experience": "Opt-in. Triggers a second navigation to /details/experience/ and adds a structured experience[] array (title/company/dates/location/description/skills per role). Costs 2 page loads instead of 1. Recommended for on-demand single-person enrichment.",
30
- "--help": "Print this help and exit."
162
+ "<profile-url>": "Required. Full LinkedIn profile URL, e.g. https://www.linkedin.com/in/<slug>/",
163
+ "--full": "Shortcut: enables ALL --with-* flags. Up to 7 page loads (main + 6 subpages). Smart-skip means missing sections aren't fetched. Recommended for own-profile audit and on-demand contact deep-dive.",
164
+ "--with-experience": "Adds /details/experience/ → structured experience[] (title, company, dates, location, description, skills).",
165
+ "--with-featured": "Adds /details/featured/ → pinned items (title, caption, url).",
166
+ "--with-education": "Adds /details/education/ → schools, degrees, dates, descriptions.",
167
+ "--with-skills": "Adds /details/skills/ → full skill list with endorsement counts (saved as skillsFull, separate from About-page topSkills).",
168
+ "--with-recommendations": "Adds /details/recommendations/ → received recommendations (recommender, title, text, date).",
169
+ "--with-certifications": "Adds /details/certifications/ → licenses & certifications (name, issuer, issue/expiry dates, credential ID).",
170
+ "--help": "Print this help and exit."
31
171
  },
32
- "banRisk": "Low. One profile per invocation = normal human profile-view behavior. With --with-experience the count is 2 loads per profile (still human-like 'click show all' pattern). Don't batch — this script intentionally takes a single URL.",
172
+ "banRisk": "Low. 1 to 7 page loads on the SAME profile = normal 'deep read' behavior. Each --with-* adds one navigation; missing sections skip their load automatically. Don't batch — this script intentionally takes a single URL.",
33
173
  "useWhen": "You need full profile data on one specific person (audit own profile, deep-dive on a contact before reaching out, verify a recruiter)."
34
174
  },
35
175
  "enrich-profiles": {
@@ -47,10 +187,27 @@
47
187
  "banRisk": "HIGH. Visits profiles without follow-up action (no message, no connect, no like) — LinkedIn has flagged accounts for exactly this pattern. Default cap 10/session, recommended run frequency at-most-once-per-day. --with-experience doubles the per-profile load count and compounds the risk.",
48
188
  "useWhen": "You have many contacts in people.json with missing About text and want to backfill in a controlled batch. Prefer 'visit' or 'funnel' flows which visit profiles WITH a purpose (lower risk) when possible."
49
189
  },
50
- "login": { "run": "node dist/src/saveLoginState.mjs", "description": "Open a browser, let you log in to LinkedIn manually, then save the cookies for all other commands. Run when sync/apply errors with 'session expired'." },
51
- "migrate-status": { "run": "node dist/migrationCli.mjs status", "description": "Show schema migration status of the local DB. Operational. Offline." },
52
- "data-cleanup": { "run": "node dist/migrationCli.mjs data-cleanup", "description": "Clean up the local DB (de-duplicate, prune stale records). Operational. Offline." },
53
- "rollback-data": { "run": "node dist/migrationCli.mjs rollback-data", "description": "Roll back the last schema migration. Use only if a migration broke something." }
190
+ "read-post": {
191
+ "run": "node dist/readPost.mjs",
192
+ "description": "Analysis-first reader for ONE LinkedIn post by URL. Default flow: you hand the URL to your AI assistant; the assistant calls this command with --json and gets structured data back to discuss with you. NOTHING is saved unless you explicitly pass --save-to-graph. Three independent output modes (combinable): human print (default), --json (AI-callable pure-stdout JSON), --out=<path> (write JSON to a file — useful when a post is too big to put into the AI's context). For huge discussions (1000+ comments) use --no-comments to skip the comment-loading scroll loop entirely, or --comments-max=N to keep only the top N.",
193
+ "usage": "ilml linkedin read-post <post-url> [--json] [--no-comments] [--comments-max=N] [--out=<path>] [--save-to-graph=<nodeId>] [--help]",
194
+ "flags": {
195
+ "<post-url>": "Required. A LinkedIn post / activity URL, e.g. https://www.linkedin.com/posts/<slug>-activity-<id>-xxxx or https://www.linkedin.com/feed/update/urn:li:activity:<id>",
196
+ "--json": "AI-callable output mode. stdout is PURE JSON (post + comments + savedToGraph if --save-to-graph was also used). Human-readable progress lines go to stderr — an AI agent capturing stdout gets clean parseable data.",
197
+ "--no-comments": "Skip comments entirely. Fast path for posts with hundreds-to-thousands of comments where the discussion isn't needed. You still get post text, author, reactions, repost count, comment count, hashtags, mentions, media flags. The comments array returns [] with commentsTruncated=true and commentsTruncatedReason='skipComments'.",
198
+ "--comments-max=N": "Keep only the first N comments (default: all). Middle ground between full discussion and --no-comments. commentsTruncated=true and commentsTruncatedOriginalCount record the cap if it kicked in.",
199
+ "--out=<path>": "Write the structured JSON to a file at <path>. Useful for huge posts: the AI agent points the command here, then reads selective parts from the file instead of loading the full content into its context. Independent of --json — combine for both file + stdout.",
200
+ "--save-to-graph=<nodeId>": "OPT-IN. Save a STABLE-FIELDS-ONLY snapshot as a child of <nodeId> in your iLiveMyLife graph. Without this flag nothing is saved. Saved fields: author / profileUrl / post text / postedAt / urn / hashtags / mentions / media flags / source URL / captured-at timestamp. INTENTIONALLY NOT SAVED: reactions count, comment count, repost count, comments[] — those drift over time and the snapshot would go stale (re-run `read-post --json` for live numbers). Title = author + first 80 chars of post; tag = 'linkedin-post'. Use different parent nodes per topic (research / leads / competitor watch) so the graph self-organises. With --json the new node id is included in JSON output under `savedToGraph`.",
201
+ "--help": "Print this help and exit."
202
+ },
203
+ "extractedFields": "One page load returns: author (name/headline/profileUrl), text, postedAt, reactions (count), commentCount, repostCount, hashtags[], mentions[] (profiles + companies linked in body), media { hasImage, hasVideo, hasArticle, hasDocument, hasPoll }, comments[] (unless skipped/capped — each has author + text + profileUrl), urn, cleanText/fullText fallbacks.",
204
+ "banRisk": "Low. One post view = a single page load = normal human browsing. --no-comments is even quieter (skips the load-more-comments scroll loop).",
205
+ "useWhen": "Default flow: AI analyses a post you're discussing — uses --json to capture, talks it through with you. Optional: --save-to-graph when the post is worth capturing into a topic node. Use --no-comments or --comments-max=N for viral posts where the discussion overwhelms your context window."
206
+ },
207
+ "login": { "run": "node dist/src/saveLoginState.mjs", "description": "Open a browser, let you log in to LinkedIn manually, then save the cookies for all other commands.", "usage": "ilml linkedin login", "useWhen": "First-time setup, or when any other command errors with 'session expired' / 'authwall'." },
208
+ "migrate-status": { "run": "node dist/migrationCli.mjs status", "description": "Show schema migration status of the local DB — current schema version vs. what the plugin expects, list of pending migrations.", "usage": "ilml linkedin migrate-status", "useWhen": "Sanity check after a plugin upgrade." },
209
+ "data-cleanup": { "run": "node dist/migrationCli.mjs data-cleanup", "description": "Clean up the local DB: de-duplicate records, prune empty stubs, fix derived fields. Offline. Idempotent.", "usage": "ilml linkedin data-cleanup", "useWhen": "Periodically (monthly) or after a sync that crashed mid-write." },
210
+ "rollback-data": { "run": "node dist/migrationCli.mjs rollback-data", "description": "Roll back the LAST schema migration using the .pre-vNNN.backup files. DESTRUCTIVE — current data is replaced with backup.", "usage": "ilml linkedin rollback-data", "banRisk": "Local DB only; LinkedIn untouched. But you LOSE every change made since the last migration.", "useWhen": "ONLY if the most recent plugin upgrade migration corrupted your data and you have no other recovery path." }
54
211
  },
55
212
  "config": {
56
213
  "sections": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilml-plugin-linkedin",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data and run AI-assisted workflows under your direction.",
5
5
  "type": "module",
6
6
  "files": [