SKILL.md
Warm-Path Deal Workspace
Turn "here is a company, here are our LinkedIn connections" into one self-contained, shareable HTML workspace that answers two questions an account team actually has: who decides, and who do we already know who can get us to them.
Most stakeholder maps stop at the first question. The second is where deals are won, and it is the part people fake - they draw a line from every connection straight to the buyer, which makes an intern look as valuable as a VP. This skill scores every route instead, so the operator spends their scarcest resource (a teammate's willingness to make an ask) on routes that will actually land.
What gets built
One HTML file, published with the Artifact tool, with a tab set per account and an account switcher on top when there is more than one:
| Tab | Contents |
|---|---|
| Overview | Account brief, headcount and funding tiles, hiring-signal chart, recent activity |
| Stakeholders | Buying group grouped by deal role; each card opens a drawer with the LinkedIn deep dive, priorities, talking points, objections, a copy-ready opener, notable posts, career timeline - every claim behind an evidence dropdown |
| Relationship Map | YOU → warm connection → named intermediary → decision maker, colour and thickness weighted by route strength, plus a ranked chain list |
| Signals | Hiring surges, job changes, intent posts, funding, newest first |
| Plays | 3-6 prioritized moves, each citing the signal or path behind it |
Inputs
Ask only for what is missing.
| Input | Required | Default |
|---|---|---|
| Target account(s) | yes | - |
| LinkedIn connection export(s) | no, but the point of the skill | skip warm paths, say so |
| Seller company + one-line product | no | infer from context |
| Deal stage | no | "Prospecting" |
| Depth | no | 6-8 stakeholders per account |
Exports arrive as a bare Connections.csv, an unzipped BasicLinkedInDataExport* folder, or the raw .zip. All three work. When the export contains Profile.csv the owner's name is read from it automatically - do not ask the user who each file belongs to when the file already says. Confirm the detected names back to them, because attributing a connection to the wrong teammate sends the intro request to the wrong person.
Phase 0 - preflight and an isolated working directory
Create a run-scoped working directory and use it for everything:
WORK="$(mktemp -d -t wpw)" # or <somewhere>/wpw-<account>-<timestamp>
mkdir -p "$WORK"
This matters more than it looks. Two runs sharing a scratch path will clobber each other's connections.json, and the failure is silent: in testing one run briefly reported that an owner had zero connections at an account when they had ten thousand. Cross-owner contamination is the worst corruption this tool can produce, because the operator ends up asking the wrong teammate for an intro to someone they have never met. One directory per run, always.
Then check Crustdata is reachable and surface the balance in one line. All Crustdata calls in this skill run through the Code Mode MCP (install.crustdata.com/mcp): plain-JavaScript execute({ code }) scripts calling await callTool(name, params) — every script opens with a // user query: ... or // model query: ... comment, branches on r.ok, and returns only the compact projection it needs (fields is a response whitelist: the result carries only the groups you list). The preflight is account_credits (free):
// model query: preflight - Crustdata reachable + credit balance
const r = await callTool("account_credits", {});
return r.ok ? r.data : { error: r.message };
Phase 1 - account brief (per account)
companyidentify (free) to resolve the company — it is fuzzy, one identifier can match several companies, so take the top confidencescore match — then one companyenrich by crustdatacompanyids with exactmatch: true and fields: ["basicinfo", "headcount", "funding", "competitors", "people", "news", "hiring", "followers", "taxonomy"] (the people group carries the decision-maker and CXO snapshot; list every group you read). Then jobsearch twice - limit: 0 with a groupby aggregation on jobdetails.category for the aggregate, then limit: 15 sorted by metadata.dateadded desc for notable roles. Then socialpostlistlive on the company (company_domain; 1 credit per post, so set limit deliberately — ~10 is plenty for the brief).
Check whether the account still exists as an independent company. Acquisitions change who holds budget and which brand people list. A recent deal belongs in the brief and in the alias list. In testing, Confluent turned out to be an IBM company mid-run, which changed the entire buying process.
Write a 3-4 sentence brief: what they do, their scale, what is changing, why now. Ground every number in the payload you just pulled.
Take the logo while you are here. basicinfo.logopermalink comes back from the free companyidentify call (and from the enrich above) at no extra credit cost. Download it, base64 it, and write it to account.logourl as a data:image/jpeg;base64,... URI - the media CDN serves these as binary/octet-stream, so a remote <img src> renders blank. The workspace shows it in the header and the account switcher; without it you get a monogram.
Phase 2 - buying group
Seed with contacts the user named plus the people group from Phase 1, then discover with personsearch filtered on experience.employmentdetails.current.companyid and senior experience.employmentdetails.current.senioritylevel, biased to the function you sell into. Resolve enum values with personautocomplete first - a wrong enum silently returns zero rows (the seniority vocabulary is a closed set: CXO, Vice President, Director, Experienced Manager, Strategic, ...).
Then run a second pass on stack ownership, not title. Seniority is a proxy for authority; owning the budget and the tools is the real thing. Search for people whose title or headline says they own the relevant systems (ops, platform, systems, enablement, tooling, "GTM systems", "talent operations"). In testing the single best-fit buyer at one account was a Senior Manager who owned 18 tools and a $5M budget, and a director-and-above sweep missed him entirely.
Trim to 6-8 people. Assign a provisional deal_role and influence (1-5). Set entity when someone sits in an acquired sub-brand rather than the parent - that is what lets a connection at that sub-brand route straight to them.
Acquisitions are where the warm paths hide. People at acquired companies often still list the original brand, so a connection at "Red Hat" never matches a search for "IBM". Web-search the acquisition history and write the full alias list to aliases.json:
{"IBM": ["ibm", "red hat", "hashicorp", "apptio", "nordcloud"]}
Multi-word aliases match as phrases, single words as whole tokens, so fragments like "edge" will not drag in unrelated firms. Watch for genuine collisions - "Observe.AI" is not the "Observe, Inc." that Snowflake bought, and both will appear.
Phase 3 - stakeholder deep dives
Spawn one sub-agent per stakeholder, batched in a single message, using references/stakeholder-agent.md. Keep raw post dumps out of the main context. Assemble into $WORK/<slug>/dossier.json following references/dossier-schema.md.
Each person's photo (basicprofile.profilepicturepermalink) is already inside the basicprofile group the sub-agent requests - free. Base64 it into photo_url on the stakeholder, same binary/octet-stream rule as the logo. Cards and drawers look like a real buying group instead of a wall of initials.
Phase 4 - connections, bench, and verification
python3 scripts/parse_connections.py --out "$WORK/connections.json" \
--input "/path/Connections.csv" \
--input "/path/Basic_LinkedInDataExport_2026.zip" # append ':Name' to override the owner
Report per-owner totals back to the user. Then verify the bench before presenting it as live routes. Exports carry the employer as of export day and go stale silently - in testing one bench member had already left the account. Spot-check the matched rows with personenrich (fields: ["basicprofile", "experience"]) and drop or flag anyone who has moved on.
Two blind spots are structural, cheap to state, and worth stating rather than shipping a map that merely looks complete:
- connections with a blank company field match no alias and are invisible
- ex-employees are invisible - the export carries current employer only, so someone who
spent a decade at the target and just left will never appear despite being an ideal broker
Phase 5 - route resolution
Read references/chain-scoring.md for the model and the honesty constraints. Three kinds of route exist, best first, and the builder detects the first two automatically:
- Zero hop - the connection is a member of the buying group. Nothing beats this and it
is easy to miss, because the bench and the buying group are built by different phases. The builder matches on LinkedIn URL then name; you get it for free.
- Date-verified shared history - the connection and a stakeholder worked at the same
company at overlapping times. This is the strongest evidenced bridge available, but only if the dates actually overlap: of 13 apparent "we both worked at X" ties in testing, only 3 survived the date check - the rest joined after the target had left. To enable it, enrich the bench members' work history and write careers.json: ``json {"https://www.linkedin.com/in/someone": [{"company": "Red Hat", "start": "2021-03", "end": "2023-08"}]} `` Keyed by LinkedIn URL or lowercase name. The builder does the date arithmetic and records non-overlapping pairs as documented dead ends so nobody rediscovers them.
- Org-layer bridge - everyone else. Resolve the senior leader of each function present in
the bench, one personsearch per (account, function) rather than per connection, and write intermediaries.json: ``json {"Acme": {"Engineering": [{"name": "...", "title": "VP, Platform Engineering", "linkedinurl": "...", "basis": "most senior Engineering leader at Acme (person_search, seniority=VP)"}]}} ` Always fill basis` with how you found them and why they qualify. Reporting lines are not in the data, so never phrase a basis as a confirmed manager relationship.
Phase 6 - signals and plays
Assemble signals[] from the hiring surge, the buying signals the sub-agents surfaced, job changes, funding and recent news, newest first with a severity. Then write 3-6 plays[] naming the connection, the intermediary and the decision maker, citing the signal that makes now the moment. "Reach out to the team" is not a play.
Phase 7 - build and publish
python3 scripts/build_workspace.py \
--dossier "Acme=$WORK/acme/dossier.json" \
--dossier "Globex=$WORK/globex/dossier.json" \
--connections "$WORK/connections.json" \
--aliases "$WORK/aliases.json" \
--intermediaries "$WORK/intermediaries.json" \
--careers "$WORK/careers.json" \
--template assets/workspace-template.html \
--title "Acme + Globex Deal Workspaces" \
--out "$WORK/workspace.html"
The builder does all the deterministic work: matching connections to accounts, detecting zero-hop and shared-history routes, scoring and ranking every chain, wiring in intermediaries, recording dead ends, and injecting the data. Check its printed summary - warm counts, strong counts, zero-hop, verified ex-colleague, named org layers - before publishing.
Then publish it. Load the artifact-design skill (required before the first Artifact call), then call Artifact with file_path = $WORK/workspace.html, a stable title, a one-sentence description and a favicon. The artifact is the deliverable; a workspace left on disk helps nobody. Re-publishing the same file path redeploys to the same URL, which is what makes iteration cheap.
Report the headline numbers: stakeholders mapped, posts analysed, warm chains and how many are strong, zero-hop routes, signals, plays.
Phase 8 - iterate
Common follow-ups: add a teammate's export and rebuild, add or re-run a stakeholder, change a deal role, widen the buying group. Each edits the inputs and re-runs Phase 7 to the same URL.
Guardrails worth holding
Never fabricate, and make the artifact prove it. Every claim carries evidence citing the payload it came from; unevidenced claims go in gaps. The evidence dropdowns are why anyone trusts the page, and one confident invention undoes them all.
"No strong chains" is a success, not a failure. If the team's network does not reach the buying function, say so plainly and call the account warm-assisted outbound. Dressing up four weak chains as a way in wastes real social capital. In testing both an unaided run and a skill run reached that verdict independently on the same account, which is the system working.
Watch for name collisions. Company names are not unique and people share names. Verify a match is the right entity before it reaches the bench, and drop procurement "strategic sourcing" roles when you are looking for talent sourcing.
House style for anything sendable. Openers and talking points get pasted into real emails: no em dashes or en dashes, and no legal-entity suffixes in company names.
Adapt the layout to the content - never let it hide anything. The brand system below is fixed; the layout is not. If real content does not fit - a long company or person name, a 12-word title, 40 stakeholders, more chains than the graph can plot - change the layout, not the content: let the card grow, wrap instead of truncating, drop to one column, widen the column, raise the cap, or put the wide thing in its own scroll container. Never solve a fit problem by clipping a card, ellipsis-ing a name, or silently dropping rows. Where a cap really is unavoidable, say so in the UI ("showing the 18 strongest of 34 routes") so the reader knows what they are not seeing. Look at the rendered page (Phase 7's self-review) and fix what is cut off before you hand it over.
Crustdata branding on rendered artifacts. The bundled workspace template carries the real Crustdata brand system - keep it, and never strip it when editing the template:
- Wordmark: the official pair ships in this skill's
assets/-crustdata-logo-light.png
(dark text, for light backgrounds) and crustdata-logo-dark.png (white text, for dark), the same files app.crustdata.com's header renders. Base64-inline the theme-appropriate variant at ~17px tall, linking to crustdata.com; the template shows both and switches them in CSS. Never hotlink a logo - rendered artifacts cannot fetch remote images.
- Color: brand purple
#5547E2(the product's primary) as the accent;#8387FF(the
product's own lightened purple) as the dark-theme accent.
- Type: Geist, the product font, embedded as a data-URI
@font-facewith the system
stack as fallback.
- Icons: Lucide, the dashboard's icon set, inlined as SVG with a
currentColorstroke
(the template's licon() helper holds the path data). No emojis in artifact UI.
Any OTHER page or document this skill renders (a one-off summary page, an exported report) carries the same system: base64-inline the wordmark pair from assets/ behind a "Powered by" eyebrow, and copy the @font-face, the accent variables, and the licon() icon helper from assets/workspace-template.html. This is conditional: chat output and data files stay unbranded, and nothing gets rendered as an artifact just to carry the mark.
Tool dependencies
- Crustdata MCP server (install.crustdata.com/mcp): a
single Code Mode MCP exposing listtools, getschema, and execute. All Crustdata data tools are reached inside an execute({ code }) plain-JavaScript script via await callTool(name, params) — author against the typed surface from getschema, but write zero type annotations in the body. Tools used here: companyidentify, companyenrich, personsearch, personautocomplete, personenrich, personcontactenrich, jobsearch, socialpostlistlive, websearchlive (alias research), account_credits
- Python 3 for
scripts/parseconnections.pyandscripts/buildworkspace.py(pure local
processing, no network calls)
- Artifact tool for publishing the workspace HTML
Bundled resources
scripts/parse_connections.py- normalises LinkedIn exports, auto-detects owner from Profile.csvscripts/build_workspace.py- bench matching, zero-hop and shared-history detection, chain scoring, HTML injectionassets/workspace-template.html- the self-contained multi-account workspace rendererreferences/dossier-schema.md- the per-account dossier contract; read before writing onereferences/stakeholder-agent.md- the deep-dive sub-agent prompt; read before Phase 3references/chain-scoring.md- how routes are scored and resolved honestly; read before Phase 5