connect
Skill3 starsChanged 33 days agoMITView on GitHub
Find people who can open doors for a picked role, classify each one (hiring manager, hiring team, recruiter, peer, unrelated), and write outreach drafts (Hook / Proof / Ask + sign-off, in the user's voice) that land as Gmail drafts. Use after a role is picked (on-deck or later). Never sends, never connects or messages on LinkedIn.
64 other projects use a version of this.
Adopted by
Most recently taken up by
- thensls/nsls-builder-toolkittheir own version, first seen Nov 8, 2026
- langchain-ai/open-swetheir own version, first seen Nov 8, 2026
- BrightGold70/skillstheir own version, first seen Nov 8, 2026
- flung-official/flung-claude-plugintheir own version, first seen Nov 8, 2026
- matthewwong525/WongStacktheir own version, first seen Nov 8, 2026
- CraftOS-dev/Agent-Apptheir own version, first seen Nov 8, 2026
Used with
Projects with it also use connect-apps, skill-share and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
ArogyaReddySkillconnect-apps
Connect Claude to external apps like Gmail, Slack, GitHub. Use this skill when the user wants to send emails, create issues, post messages, or take actions in external services.
Also in 19 projects that use this
ArogyaReddySkillskill-share
A skill that creates new Claude skills and automatically shares them on Slack using Rube for seamless team collaboration and skill discovery.
Also in 17 projects that use this
ComposioHQSkillagent-deep-links
Build, validate, and troubleshoot deep links for Codex, Cursor, VS Code, Visual Studio, and similar tools. Use when users ask for clickable links (especially in Slack) that open threads, files, folders, or app settings.
Also in 3 projects that use this
ComposioHQSkillhelium-mcp
Search real-time news with bias scoring, get live stock/ETF/crypto data with AI analysis, ML options pricing, balanced news synthesis, and meme search via the Helium MCP server.
Also in 3 projects that use this
trendinuxSkilltwitter-algorithm-optimizer
Analyze and optimize tweets for maximum reach using Twitter's open-source algorithm insights. Rewrite and edit user tweets to improve engagement and visibility based on how the recommendation system ranks content.
Also in 12 projects that use this
LangChainSkilllangsmith-fetch
Fetches LangSmith traces for debugging agent behavior. Use when troubleshooting agent issues, reviewing conversation history, or investigating tool calls.
Also in 18 projects that use this
- +1 more from ComposioHQ
- +5 more from ArogyaReddy
Other versions64 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 64
- ComposioHQ/awesome-claude-skills★ 75ksame name
- ComposioHQ/awesome-codex-skills★ 17ksame name
- composio-community/awesome-codex-skills★ 16ksame name
- langchain-ai/open-swe★ 11ksame name
- suboss87/FDEOps★ 940same name
- indranilbanerjee/digital-marketing-pro★ 934same name
- ArogyaReddy/ComposioHQ-awesome-claude-skills★ 45same name
- frostant/awesome-claude-skills★ 44same name
- jexchan/dailyup-second-brain-starter★ 33same name
- microsoft/Employee-Self-Service-Agent-Developer-Kit★ 29same name
54 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. 0 are common, 9 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 56 edited versions we read keep these.
9
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 15
Collect hints (judgment): Read the JD (roles/<role_id>.md) and, if useful, the company team page.
- Very specific: line 20
Hiring team: if the posting is on LinkedIn and lists "Meet the hiring team", note those names (hiring_team: true).
- Distinctive: line 42
Never contact mail servers or verification sites: no SMTP, no mailbox probe, no test email, no email-checker or verification website or …
- Distinctive: line 54
No em dashes, no en dashes.
- Very specific: line 65
When people.find returns nobody usable, Connect falls back automatically (#78).
- Very specific: line 67
Hiring team or recruiting address: Search the JD, the careers page, the dossier (companies/<slug>/dossier.md) and the company site for a …
- Very specific: line 69
Call people.fallback { role_id, team_inbox: [{ email, name?, source: "jd" | "careers" | "dossier" | "company_site", url?
- Distinctive: line 90
Read the Gmail draft and keep its subject and body exactly as they are (the user may have edited them in Gmail).
- Very specific: line 91
Update that same draft with To = to.email.
- Very specific: line 116
Never send an email. outreach.send is unimplemented on purpose.
- Distinctive: line 117
Never create a Gmail draft without To for a recipient with no address at all (#132): it stays a vault draft.
- Distinctive: line 118
Never connect, message, follow, like or InMail on LinkedIn, and never click a LinkedIn button.
- Distinctive: line 119
Never store more than name, headline, URL, relationship and email (with its source and confidence).
- Distinctive: line 121
Never contact a mail server or a verification site (SMTP, mailbox probes, test emails, email checkers).
- Distinctive: line 122
Never run people.find on a role the user has not picked (an inbox or archived role).
- Distinctive: line 123
Never commit anything from the vault.
“Common” is measured: kept by at least 6 in 10 of the 56 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
connect
Stage 06 of the flow: runs on pick, outputs contacts + Gmail drafts.
You supply judgment (who is in the JD, who you read on LinkedIn, the draft text). Code does mechanics (URL allowlist, caps, budget, dedupe, tiers, files). Every write is an op.
Steps
- Check the role is picked.
roles.get { role_id }. Starting Connect (runs.startorpeople.find) on anactiveorheldrole picks it (-> on-deck,role_picked, #105);inboxandarchivedroles are refused. - Collect hints (judgment). Read the JD (
roles/<role_id>.md) and, if useful, the company team page. List people named there: name, headline, URL or email if shown. Do not search for personal emails (the email step below looks for work addresses only). - Read LinkedIn (judgment, read-only, #82). Only when the prompt's LinkedIn line says it is on (
config.linkedin.mode: "live") and only for this picked role. Use {{browser.name}}'s read and navigate tools (listed under Tools); nothing that clicks, types or runs script is granted. Start with{{browser.tabs}}and reuse the tab this Chrome session created ({{browser.navigate}}it); create one with{{browser.new_tab}}only if none is usable, so no empty extra tab is left. Then, waiting the stated delay (min_delay_sto 2x) before every page, read in this order and stop at the page cap (max_pages_per_run) or the profile budget the prompt states:- Your 1st-degree connections at the company:
https://www.linkedin.com/search/results/people/?keywords=<Company>&network=["F"]. - The company People page, filtered by the role title's keywords:
https://www.linkedin.com/company/<linkedin-slug>/people/?keywords=<title keywords>. - Mutuals for the top 2nd-degree candidates (at most
max_mutual_expansions): the "N mutual connections" link on their card (a/search/results/people/URL). - Hiring team: if the posting is on LinkedIn and lists "Meet the hiring team", note those names (
hiring_team: true). Read only names, headlines, profile URLs (https://www.linkedin.com/in/<slug>), the degree badge (1st, 2nd, 3rd+) and the mutual. Never open a profile, messaging, invitation, follow or feed page; never press Connect, Message, Follow, Like or any button. A sign-in wall, checkpoint, captcha or "unusual activity" page: stop at once, read nothing more, and report it (skipped: "checkpoint"or"signed_out"). {{browser.none}}:skipped: "no_browser".
- Your 1st-degree connections at the company:
- Find.
people.find { role_id, hints, linkedin: { pages_opened, skipped? } }. Hints:- From the JD and team page:
{ name, headline?, linkedin_url?, email?, source: "jd" | "company_site" }. - From your LinkedIn read:
{ name, headline, linkedin_url, source: "linkedin", degree: "1" | "2" | "cold", via?: { name, headline, linkedin_url, tie? } (2nd degree, the mutual), hiring_team?: true }. team_sideandbrand(judgment, #132): setteam_side: truefor a person who is not on the hiring team but is on the team side: they work at the company or one of its product brands (a product sold under a different name: check the dossier's aliases / brands,companies/<slug>/dossier.md), in the role's org or an adjacent function on that product (product, design, engineering leadership). Example: the Head of Design at the company's product brand for a product role. Addbrand: "<Brand>"when they work under a brand name that differs from the company name. Leave both out for anyone else (a peer in an unrelated org, someone at another company). Team side only ever adds, likehiring_team.via.tie(judgment, #98):{ same_company, same_industry, history }, how close the mutual is to this person:same_companywhen the mutual's headline puts them at the target's company,same_industrywhen it is the same industry,historywhen you saw they worked or talked together. Omit what you did not see (default false). Always passlinkedin.pages_opened(0 if you opened none) and, if you read nothing,skipped(no_browser,signed_out,checkpoint,capped,not_found,error). The engine keeps only/in/<slug>profile URLs, counts every person and mutual against the daily budget, drops hints beyond it (capped: true: stop for today, do not work around it) and recordslinkedin_mode: "agent"only when you really read LinkedIn. Adddry_run: truefirst if the user wants a preview.
- From the JD and team page:
Classify. For each found contact:
people.classify { contact_id, role_id }. The resolver answers; low confidence lands in Needs you with the pick shown. Tier is mechanics:Hiring team (hiring manager, hiring team, recruiter) Not hiring team 1st degree warm_hiring: ask for a conversation about the rolewarm_other: coffee chat, or intro to the hiring manager2nd degree bridge: note to the mutual + forwardable blurb (or a direct note, see below)same Cold cold_hiring: their problem, one proof, one askcold_other: skip unless the user asks- Pick the best path. A 2nd-degree hiring-team or team-side (#132) contact with a mutual has two paths (#98), ranked by code in
roles.get(contacts[].paths): direct (a note to the target, tiercold_hiring) or bridge (an intro ask to the mutual, tierbridge). Direct wins when the target's email is confirmed or likely and the mutual is a weak tie; bridge only for a strong mutual with an email; a bridge to a mutual with no email isblockedwhile a direct path exists. Draft thechosenpath (the user may have switched it withpeople.set_path); the other open path may be drafted too when the user asks (both are kept).outreach.draftrefuses a blocked bridge. Run the email step for the target first: their address decides whether direct is reachable. Then draft for the top contact per tier, in order warm_hiring, warm_other, bridge, cold_hiring. Usually one to four drafts per role. Skipcold_otherunless asked. No usable contact (none found, or every one iscold_other): follow "No usable contact" below; the role must never stall in Connect. - Email (judgment: what you saw; code: the guess and the check). For each contact you will draft to (bridge: the mutual; a 2nd-degree hiring target: the target and the mutual, so the paths rank on real addresses), find addresses, read-only:
- Gmail (read-only here:
{{gmail.search}},{{gmail.thread}},{{gmail.message}}): threads with the person (from:/to:their name) and signatures. - Inbox peers (#99): search the user's own mail for
@<domain>and note real addresses at the company domain in the from / to / cc headers, each with the display name when the header shows one. Keep only the address (and that name, to infer the format); never copy a subject, body or anything else. - Public pages: the JD, the company team page, talks, GitHub, a personal site.
Then call
people.find_email { contact_id, role_id, hints: [{ email, name?, source: "gmail" | "linkedin" | "public" }], evidence: [{ kind: "inbox_peer" | "public_mention", address, source, url?, name? }], domain? }.hints= addresses that are this person's (or that you saw with whose they are);evidence= inbox peers (source: "gmail") and public mentions (source: the page,url). Passdomainwhen the company has none on file (dossier or website). The op keeps a known address, else learns the format (first.last,flast,first,firstl,f.last,last), ranks candidates and checks each one passively: syntax, the domain's MX (or A) record, disposable / role / free lists, and the evidence (peers in the candidate's format, an exact sighting). An exact match, or 2+ peers agreeing with none conflicting, is confirmed without asking; a domain with no mail records drops the candidate; anything else is stored as a guess and goes to Needs you, where the user checks it by hand. If the result is not confirmed, look for the exact candidate addresses (candidates[].email) in the user's mail ("<address>") and on public pages you can read, and callpeople.find_emailagain with what you found asevidence(it replaces the pending pick). Never guess the address yourself. Never contact mail servers or verification sites: no SMTP, no mailbox probe, no test email, no email-checker or verification website or API, no paid finder. The user does the manual check in Needs you.
- Gmail (read-only here:
Write the draft (judgment). The draft must read like a real email from the user, in their voice. Gather the writer inputs, keeping the two kinds of facts apart:
- CONTEXT (facts about them, usable for the hook): the contact's headline, the role's JD (
roles/<role_id>.md, the JD section), the company dossier (companies/<slug>/dossier.md: snapshot, why, hooks, news). Never present CONTEXT as a fact about the user. - STORY (
profile/story.mdplus the Story component data): the only source of facts about the user. - VOICE (
profile/voice.mdplus the Voice component:style,rules,signoff,samples): tone only. Treat every writing sample as a style exemplar: match its tone, rhythm and phrasing; never reuse its facts, names or numbers. Use all of them, even when voice.md has text.
The engine's own writer prompt (
buildWriterPromptinpackages/engine/people/templates.ts) uses exactly these inputs and rules. Write:- Hook: opens with a greeting ("Hi Maya,"), then one or two lines about their world from CONTEXT (reframe what they care about). Max 320 chars. A number here must appear in STORY or CONTEXT.
- Proof: two or three sentences, first person, from STORY only, tied to what the role needs. At most 3 sentences, max 560 chars. Never a number that is not in STORY.
- Ask: one small, specific ask (the close). Max 240 chars; bridge max 560 (includes the forwardable blurb).
- Sign-off: in the user's voice, max 80 chars. Default: Voice
signoff, else "Thanks," plus the first name from Story (omitsignoffto get the default). - Subject: short, plain. Max 80 chars.
- No em dashes, no en dashes. No flattery ("huge fan", "I admire", "amazing team" are refused). No facts beyond STORY.
Bridge drafts are addressed to the mutual, not the target. A direct note to a 2nd-degree target (hiring team or team side) is a
cold_hiringnote to them (no mention of the mutual needed).
- CONTEXT (facts about them, usable for the hook): the contact's headline, the role's JD (
- Save.
outreach.draft { contact_id, role_id, tier, subject, hook, proof, ask, signoff? }.contact_idis the target; the engine files the draft under the person it is addressed to (a bridge intro under the mutual, withabout_contact_id= the target), one draft per recipient. Agent-written drafts pass the samecheckDraftas engine-written ones: it refuses rule breaks with the exact issue (em_dash,too_long,too_many_sentences,ungrounded_number,flattery,empty); fix and call again. - To Gmail. For each saved draft, follow "Gmail draft step" below in this same run (
{{gmail.create_draft}}is granted to everyconnectrun). It creates a Gmail draft and stops. The user opens Gmail and sends. A draft whose recipient's address is a guess waiting in Needs you (outreach.get'sto.decision_idis set) gets no Gmail draft now (#99): leave it saved in the vault; once the address is confirmed the engine queues the Gmail draft step for it with To filled. Never create a Gmail draft without To (#132): a recipient with no address at all (to.emailandto.guessboth null) gets no Gmail draft; the draft stays in the vault and the Outreach screen offers Copy note and Open profile for LinkedIn, where the user writes to them by hand (pp never messages on LinkedIn).outreach.to_gmailandruns.startrefuse it; once an address is found and confirmed (people.find_email), the engine queues the Gmail step as usual.
Chrome tabs
At the end of a run that used Chrome (done or failed, #107): close every tab you opened or used with {{browser.close_tab}}, never a tab the user already had open, and report them on the finish: runs.finish { run_id, status, tabs: { opened: [<every tab id you used>], closed: [<ids you closed>], left_open: [] } }. The run log lists them. A connect run leaves no tab open.
No usable contact
When people.find returns nobody usable, Connect falls back automatically (#78). You find (judgment); people.fallback picks (code), in this order:
- Hiring team or recruiting address. Search the JD, the careers page, the dossier (
companies/<slug>/dossier.md) and the company site for a team mailbox (recruiting@,talent@,jobs@,careers@,hiring@) or a named recruiter with a work address. Public pages only; never a personal address. - The likely hiring leader. From the dossier: who this role most likely reports to (e.g. the CPO or VP Product), with headline and LinkedIn URL if the dossier has it.
- Call
people.fallback { role_id, team_inbox: [{ email, name?, source: "jd" | "careers" | "dossier" | "company_site", url? }], leader?: { name, headline?, linkedin_url? }, domain?, email_hints?, reason? }with everything you found (or nothing). The engine also scans the JD and dossier for team mailboxes, keeps only addresses at the company domain, and then:team_inbox: a contact "Hiring team" (or the named recruiter),email_source: public, linkedcold_hiring. Draft to it.cold_leader: the leader as acold_hiringcontact;people.find_emailguesses the address (flagged as a guess; an unsure one waits in Needs you, so the Gmail draft has no To until confirmed). Draft to them.skipped: nothing usable; the application moves from Connect to Fill. Stop: no draft. (apply.skip_connect { role_id, reason }does the same on its own; the UI's Skip calls it.)
- Fallback draft (judgment). The draft is a shortened version of the tailored cover letter (
applications/<role_id>/cover_letter.md), in the user's voice: Hook = greeting ("Hi<Company>team," for a team inbox, else the first name) plus why this role; Proof = two or three first-person sentences from the cover letter; Ask = team inbox: pass it to the hiring manager; leader: a short call. The cover letter counts as a fact source next to STORY for these drafts only (it passed the tailor fact check); everything else in step 8 applies, andoutreach.draftruns the samecheckDraft. Then the Gmail draft step as usual.
Gmail draft step
The engine has no Gmail client. You create the draft with your own Gmail draft tool, then record it. This works from any MCP client that has a Gmail draft tool next to the pp server: {{gmail.source}} A full connect run does this step itself for each draft it saved. The web UI's Create Gmail draft starts a headless connect run for one outreach (runs.start { skill: "connect", outreach_id }) that does only this step (for a draft saved earlier, or one the user edited).
- Read the draft.
outreach.get { outreach_id }: subject, hook, proof, ask, signoff andbody. No subject: the role title plus " at " and the company (roles.get). - Recipient.
outreach.getreturnsto: the contact, or the mutual for bridge drafts (never the target). To =to.email. Whenoutreach.gmail_draft_idis set (the draft is already in Gmail), create nothing (runs.startrefuses the step too). Whento.decision_idis set (a guessed address waiting in Needs you), create nothing: the engine queues this step once the address is confirmed (outreach.to_gmailandruns.startrefuse it meanwhile). Whento.emailandto.guessare both null (no address at all), create nothing: finish ok withsummary: { outreach_id, skipped: "no email" }; never a Gmail draft without To (#132:outreach.to_gmailandruns.startrefuse it). Whento.emailis null otherwise (an unconfirmed guess not in Needs you), leave To empty:to.reasonsays why. Never useto.guess. - Create a Gmail DRAFT. Body = the
bodyfield exactly as returned: greeting line, blank line, Hook, blank line, Proof, blank line, Ask, blank line, sign-off (plain text, built from the stored blocks, which the user may have edited). Create it once. Never send, reply or forward. No Gmail draft tool in this session: create nothing and finish withruns.finish { run_id, status: "error", {{missing.error}}{{gmail.create_draft}}" }{{connect.draft_retry}} - Record it.
outreach.to_gmail { outreach_id, gmail_draft_id, confidence }. Medium impact: withconfidencethe policy grid decides; without it, or unsure, it waits in Needs you. It re-checks the writing rules, stores the id, sets the outreachin_gmailand moves the application to Fill. - Finish (headless run):
runs.finish { run_id, status: "ok", summary: { outreach_id, gmail_draft_id, to, to_reason } }(tonull andto_reasonset when the draft has no To).
Gmail To step
A Gmail draft created with no To (before #99, or while its address was still a guess) is fixed once the address is confirmed: the engine queues runs.start { skill: "connect", outreach_id } for it and that run is this step only (summary_json.step: "gmail_to"). It is the only run granted the Gmail connector's get draft and update draft tools (not part of the full connect allowlist below); create, send, reply, forward, delete, trash and labels stay denied.
outreach.get { outreach_id }: the draft id isoutreach.gmail_draft_id, To =to.email.- Read the Gmail draft and keep its subject and body exactly as they are (the user may have edited them in Gmail).
- Update that same draft with To =
to.email. Never send. outreach.to_gmail { outreach_id, gmail_draft_id, confidence }with the same id: the engine stores the To.runs.finish { run_id, status: "ok", summary: { outreach_id, gmail_draft_id, to } }.
Tools
A headless run pre-approves exactly these (SKILL_GRANTS in packages/engine/runners/grants.ts; a test keeps this list and that one equal) and denies everything else. If one you need is denied or missing, do not work around it: runs.finish { run_id, status: "error", summary: { error: "tool denied: <tool>", denied_tools: [...] } }. {{connector.tools}}
{{tool:pp}}: the pp MCP tools (every Perfect Placement read and write is an op).{{tool:gmail.search}}: search Gmail for threads with a contact or the company domain.{{tool:gmail.thread}}: read a thread (addresses, signatures).{{tool:gmail.message}}: read one message.{{tool:gmail.create_draft}}: create the Gmail draft (Gmail draft step). Never send.{{tool:browser.tabs}}: see the open Chrome tabs and reuse the session's tab (LinkedIn read, step 3).{{tool:browser.new_tab}}: open a tab for the LinkedIn read, only if the session has no usable tab.{{tool:browser.close_tab}}: close the tabs this run opened or used, at the end (never the user's tabs).{{tool:browser.navigate}}: open an allowlisted LinkedIn people page.{{tool:browser.text}}: read the people cards as text.{{tool:browser.tree}}: read the page's structure when the text is not enough.{{tool:browser.find}}: locate the people list or a "mutual connections" link (read only, never click).
{{connect.tools_note}}
Never
- Never send an email.
outreach.sendis unimplemented on purpose. - Never create a Gmail draft without To for a recipient with no address at all (#132): it stays a vault draft.
- Never connect, message, follow, like or InMail on LinkedIn, and never click a LinkedIn button. Read people listings only, within the caps; do not try another way.
- Never store more than name, headline, URL, relationship and email (with its source and confidence).
- Gmail is search, read and draft only. Never send, reply, forward, delete, trash or label.
- Never contact a mail server or a verification site (SMTP, mailbox probes, test emails, email checkers). Evidence is passive: the user's own mail (addresses only) and public pages.
- Never run people.find on a role the user has not picked (an inbox or archived role); starting Connect on an active or held role is the pick.
- Never commit anything from the vault. Placeholders only in examples.
First live LinkedIn run (owner present): see RUNBOOK.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

