One .zip per skill. Inside: SKILL.md with the intent and the frontmatter, and the code that runs in the page.
Settings → Personalize → Skills. This is Cowork's own skill library, shared across every chat.
Click Add and choose the file you just downloaded. Cowork installs it once; no CLAUDE.md, no per-project setup.
The skill stays available in every session. Cowork runs it in your own Chrome, riding the session you are already signed into.
ADVANCED people search on LinkedIn covering ALL the filters of the 'All filters' panel, ONE or MANY searches with AUTO-PAGINATION.
{"queries":"array of objects; each one requires page (pages optional, max 100). Rest of the filters optional: keywords, firstName, lastName, title, company, schoolText, network, geoUrn, currentCompany, pastCompany, school, industry, serviceCategory, profileLanguage, connectionOf, followerOf, openToVolunteer"}{"count":"int","results":"array of { query, pagesFetched, count, results:[{ name, degree, slug, profileUrl, headline, location }] }","skipped":"int (if applicable)"}ADVANCED people search on LinkedIn covering ALL the filters of the 'All filters' panel, ONE or MANY searches with AUTO-PAGINATION. Single PARAM: queries = array of objects. Each query accepts ALL these fields (all optional except page):
skipped). ~10 results per page. Returns { count, skipped?, results:[{ query, pagesFetched, count, results:[{ name, degree, slug, profileUrl, headline, location }] }] }. Requires a session. Regex parser over SSR HTML; if the markup or a param name changes, recapture from the 'All filters' panel.params. It does the in-page work and returns the JSON in returns.( async (root, params) => { let qs = params && params.queries; if (typeof qs === 'string') { try { qs = JSON.parse(qs); } catch(e){ qs = []; } } if (!Array.isArray(qs)) qs = []; const CONC = 20; const DELAY = 150; const MAXTASKS = 100; const enc = encodeURIComponent; const sleep = ms => new Promise(r=>setTimeout(r,ms)); const wrapArr = v => { if (v == null) return ''; if (Array.isArray(v)) v = v.join(','); v = String(v).trim(); if (!v) return ''; if (v.charAt(0) === '[') return v; return '[' + v.split(',').map(s => '"' + s.trim() + '"').join(',') + ']'; }; const buildUrl = (q, pg) => { const parts = ['keywords=' + enc(q.keywords || '')]; const arrKeys = { geoUrn:'geoUrn', currentCompany:'currentCompany', pastCompany:'pastCompany', school:'schoolFilter', industry:'industry', serviceCategory:'serviceCategory', profileLanguage:'profileLanguage', network:'network', followerOf:'followerOf' }; for (const k in arrKeys) { const w = wrapArr(q[k]); if (w) parts.push(arrKeys[k] + '=' + enc(w)); } const strKeys = { firstName:'firstName', lastName:'lastName', title:'title', company:'company', schoolText:'schoolFreetext', connectionOf:'connectionOf' }; for (const k in strKeys) { const v = (q[k] == null ? '' : String(q[k]).trim()); if (v) parts.push(strKeys[k] + '=%22' + enc(v) + '%22'); } if (q.openToVolunteer === true || q.openToVolunteer === 'true' || q.openToVolunteer === 1) parts.push('openToVolunteer=' + enc('["true"]')); parts.push('page=' + enc(String(pg))); parts.push('origin=FACETED_SEARCH'); return 'https://www.linkedin.com/search/results/people/?' + parts.join('&'); }; const parse = (html) => { const re=/href="https://www.linkedin.com/in/([^"]+)"\s+componentkey="[0-9a-f-]+"/g; const starts=[]; let m; while((m=re.exec(html))){ const slug=m[1].split(/[/?#]/)[0]; starts.push({slug, idx:m.index}); } const noise=/^(Connect|Follow|Send message|Message|View profile|Premium|Status)$/i; const out=[]; for(let k=0;k<starts.length;k++){ let chunk=html.slice(starts[k].idx, (starts[k+1]?starts[k+1].idx:starts[k].idx+2500)); chunk=chunk.slice(chunk.indexOf('>')+1); const lines=chunk.split(/<[^>]+>/).map(s=>s.replace(/&/g,'&').replace(/'/g,"'").replace(/"/g,'"').trim()).filter(Boolean); if(!lines.length) continue; let name=lines[0], degree=null; const dm=name.match(/•\s*(1er|2do|3er.|1st|2nd|3rd.|\d+º)/); if(dm){degree=dm[1];name=name.replace(/•./,'').trim();} const di=lines.findIndex(l=>/^•/.test(l)); if(!degree && di>=0) degree=lines[di].replace(/[•\s]/g,''); const rest=lines.slice(1).filter(l=>!noise.test(l) && !/^•/.test(l) && l.length>1 && l!==name); const headline=rest[0]||null; const location=rest.find(l=>/,| y alrededores|Argentina|Spain|Espa|United|Francia|Brasil|Chile|M[eé]xico|Egypt|Egipto/.test(l) && l!==headline) || null; out.push({slug:decodeURIComponent(starts[k].slug), name, degree, headline, location, profileUrl:'https://www.linkedin.com/in/'+starts[k].slug}); } return out; }; let tasks=[]; qs.forEach((q,qi)=>{ if (q.page===undefined || q.page===null || String(q.page).trim()==='') return; const start=parseInt(String(q.page),10)||1; let n=parseInt(String(q.pages||'1'),10)||1; if(n<1)n=1; if(n>100)n=100; for(let pg=start; pg<start+n; pg++) tasks.push({qi,pg,q}); }); let skipped=0; if(tasks.length>MAXTASKS){ skipped=tasks.length-MAXTASKS; tasks=tasks.slice(0,MAXTASKS); } const fetched=new Array(tasks.length); let next=0; const worker = async () => { while(true){ const i=next++; if(i>=tasks.length) break; const t=tasks[i]; if(DELAY>0) await sleep(DELAY0.5 + Math.random()*DELAY); try{ const r=await fetch(buildUrl(t.q,t.pg),{credentials:'include'}); const h=await r.text(); fetched[i]={qi:t.qi,pg:t.pg,people:parse(h)}; } catch(e){ fetched[i]={qi:t.qi,pg:t.pg,people:[],error:String(e)}; } } }; await Promise.all(Array.from({length:Math.min(CONC,tasks.length||1)}, worker)); const results=qs.map((q,qi)=>{ if (q.page===undefined || q.page===null || String(q.page).trim()==='') return {query:q,error:'page is required',count:0,results:[]}; const mine=fetched.filter(f=>f&&f.qi===qi).sort((a,b)=>a.pg-b.pg); const seen=new Set(); const merged=[]; mine.forEach(f=>f.people.forEach(p=>{ if(p.slug && !seen.has(p.slug)){seen.add(p.slug); merged.push(p);} })); return {query:q, pagesFetched:mine.length, count:merged.length, results:merged}; }); const ret={count:results.length, results}; if(skipped) ret.skipped=skipped; return ret; } )
Response text contains "LinkedIn".