agentleFS
CodesWhatCodesWhat/careerrat/.agents/skills/optimize-linkedin/SKILL.md

optimize-linkedin

Skill4 starsChanged 9 days agoMITView on GitHub

Read the candidate's LinkedIn profile through the session browser, diff it against their targeting + evidence, and propose honest, evidence-backed rewrites of the headline / About / experience / skills / Featured, then write the approved edits back confirm-first per field. Dry-run preview is the default and always the first move. User-initiated, local-only. Capabilities = profile_optimize (read+suggest), profile_apply (write-back); platform = linkedin.

The file

Show its rules and text

We split this skill into the rules it gives an agent

18 rules. About 0 are common, 6 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read + suggest: careerrat automation enable profile_optimize --write then careerrat automation enable profile_optimize linkedin --write

    line 62
  2. Very specific:

    If candidate/ is not set up, run ingest-profile first — there is no truth bank to diff against otherwise.

    line 87
  3. Distinctive:

    Read and record the current text, per surface, so the diff is exact:

    line 114
  4. Very specific:

    Trace every line to evidence: No proposed bullet, metric, or claim without a backing evidence.yml claim.

    line 139
  5. Very specific:

    Voice + length: Keep the About short and in the candidate's register — no corporate wall-of-bullets.

    line 174
  6. Very specific:

    No placeholder brackets: If a detail is unknown, go generic or omit it — never emit [Company], [Role], [Name], or any bracket token (the …

    line 177
  7. Very specific:

    Write the full set to a document the candidate can act on, in the loose _linkedin/ bucket (per the Artifact Contract — a profile doc isn't …

    line 180
  8. Very specific:

    Only with profile_apply allowed, and only after the user approves specific fields.

    line 207
  9. Very specific:

    If the in-app pass already staged fields, read the batch first (careerrat data linkedin-proposals latest --status reviewed, or the default …

    line 212
  10. Distinctive:

    Never edit anything other than the candidate's own profile.

    line 250
  11. Very specific:

    If the field was not already approved in the app, record the user's live yes first (careerrat data linkedin-proposals approve --batch <id> …

    line 260
  12. Distinctive:

    Only mark a field applied after the reload-and-re-read confirms it — never on the write attempt alone.

    line 271
  13. Distinctive:

    Every decision bumps the batch version, and the command above prints the updated batch in its response.

    line 274
  14. Distinctive:

    If the pass surfaces nothing the candidate should act on, say so — don't manufacture changes.

    line 289
  15. Very specific:

    On by default, two separate switches: Run a browser pass only where careerrat automation status --json shows the capability allowed: true …

    line 296
  16. Distinctive:

    Dry-run preview is the default and the first move: Always produce the full read-only before→after set before writing anything.

    line 303
  17. Very specific:

    Honesty firewall (hard): Every proposed or written line traces to an evidence.yml claim.

    line 311
  18. Very specific:

    Domain-neutral: No hardcoded companies, roles, titles, codenames, or candidate-specific values in this skill.

    line 355

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

optimize-linkedin

A LinkedIn profile pass driven through the session browser. It reads the candidate's live profile, diffs it against their own truth bank (targeting.yml + evidence.yml), and proposes honest, evidence-backed rewrites so the profile reads for the roles they actually want. It can then write the approved edits back, one confirmed field at a time.

Two separately-switched capabilities on the linkedin platform, both on by default:

  • profile_optimize (read + suggest) — reads the profile and produces a full before→after diff. Read-only. Never touches the live profile.
  • profile_apply (write the approved edits) — applies the approved rewrites in the live profile through the session browser, confirm-first PER FIELD.

Reading/suggesting and writing carry different intent, so they have independent switches checked through the same mayRun(); the write-back switch never changes what the per-field confirmation in STEP 5 requires. Both halt on captcha / 2FA / login-wall.

Runs under AGENTS.md. These contracts bind without being restated here: Privacy Invariant (current_base never outbound), Honesty Firewall, Placeholder/Bracket Ban, Gate Write-back, Domain-Neutral Rule, Browser Automation Contract, Activity Pulse logging, Tracker verify+snapshot, and Sent-Clears-Draft. Inline reminders at point-of-use are intentional; standalone restatements point back to the relevant AGENTS.md section. Bare candidate/, workspace/, config/, and .internal/ paths below are symbolic; resolve them per AGENTS.md's Path Resolution rule.

Dry-run preview is the default and always the first move. The skill always produces the full read-only before→after set first — everything it would flag and change, nothing touched — so the candidate sees the whole picture before a single field is written. Applying is a deliberate second step, confirmed per field. This is the same dry-run-by-default posture as apply-job's authenticated_apply_preparation: preview the diff, then write. It also means the skill runs as a pure read-only audit even when profile_apply is off.

STEP 0: Capability check

Editing a public profile is modifying public-facing content, so STEP 5 keeps its per-field confirmation. The capability check below only says whether each part is on.

Run:

careerrat automation status --json

Inspect capabilities.profile_optimize and capabilities.profile_apply for platform linkedin. For each, allowed: true means neither the capability's switch nor linkedin's switch is turned off. This is the verdict from mayRun() in src/core/automation/consent.mjs; never re-derive it in prose. Both are on by default.

Resolve the run mode from the two verdicts:

  • profile_optimize allowed → run the read + suggest pass (STEPS 1–4). This is the whole skill for most users.
  • profile_apply ALSO allowed → after the user approves specific fields in STEP 4, you may write those fields back (STEP 5, confirmed per field). profile_apply allowed but profile_optimize not is not a valid run, you must read and propose before you can apply.
  • Neither allowed → you can still run the lean / no-browser fix-doc path (see below), which drives no logged-in session. For the browser-driven pass, say it is turned off (if readError is set, say the settings file can't be read and name it), give the commands, and skip it:

    • Read + suggest: careerrat automation enable profile_optimize --write then careerrat automation enable profile_optimize linkedin --write
    • Write-back: careerrat automation enable profile_apply --write then careerrat automation enable profile_apply linkedin --write

This skill is always user-initiated. Never run it unprompted or on a schedule.

STEP 1 — LOAD THE CANDIDATE TRUTH BANK

The profile is rewritten from the candidate's own data, never invented. Read:

  • candidate/profile.yml — domain, toolchain, location.*, and the outbound-safe comp fields. compensation.current_base is private — it must never appear in any suggested or written profile copy.
  • candidate/targeting.yml — role_buckets / target titles, keep_signals, cut_signals. This is what the profile should read for — the target side.
  • candidate/evidence.yml — the claim bank. Every proposed line must trace to a claim here (run careerrat evidence list if useful). No claim → no line. This is the same honesty firewall tailor-application enforces.
  • candidate/honesty.yml — education policy, tools.confirmed / tools.do_not_claim, claims.do_not_fabricate. Overclaim caps apply to profile copy exactly as they do to a résumé.
  • candidate/writing-style.md (+ workspace/writing-samples/) — voice/register, so the rewrite sounds like the candidate, not like corporate filler.

If candidate/ is not set up, run ingest-profile first — there is no truth bank to diff against otherwise.

STEP 2 — OPEN THE PROFILE IN THE SESSION BROWSER (read-only)

Navigate to the candidate's own LinkedIn profile in the session browser. The session browser is Layer 3 per docs/BROWSER.md. Keep the provider on auto unless the user explicitly changes it; CareerRat resolves the current session browser and reuses its signed-in session.

Snapshot or read the current page before reading anything. Drive the live DOM turn-by-turn; never rely on hardcoded selectors — the same model as apply-job and ingest-messages.

On a login wall, captcha, 2FA prompt, or any unexpected interstitial, halt immediately and ask the user to complete it. Never bypass or automate around an auth challenge.

Save any screenshot or scraped fragment under workspace/ only.

Lean / no-browser fix-doc path. If no session browser is configured, the user wants suggest-only, or the capability is turned off, skip the live session entirely. Work from a read-only source instead — a Layer-1 WebFetch of the public profile, or a profile the user pastes in, and produce the same before→after document (STEP 4). No browser needed, no write-back. This is the default tier and a valid complete run on its own.

STEP 3 — READ THE CURRENT PROFILE SURFACES (capture verbatim)

Read and record the current text, per surface, so the diff is exact:

  • Headline — the current line under the name.
  • About — the full summary text.
  • Experience — each role's title, employer, dates, and description body.
  • Skills — the current skill list (note the 50-skill cap and which are pinned/top).
  • Featured — the current Featured items (cards/links) and their titles/descriptions.
  • Licenses & certifications — each credential, its issuer, and issue/expiry dates.
  • Open-to-work / job preferences — the target-titles field (visible only to the user).
  • Contact info — websites and their labels/types.

Capture each verbatim. Don't propose anything yet — STEP 4 owns the diff.

STEP 4 — DIFF + PROPOSE (the dry-run preview — always produced first)

For each surface, write the candidate's current text beside an honest, evidence-backed rewrite. Apply these rules — they are the crux of the skill:

Honest-vs-target field rule (load-bearing). Headline / About / Experience describe what the candidate ACTUALLY is and has done — strictly honest, no aspirational titles. Target titles the candidate is seeking (e.g. a role they want but haven't held) belong in the Open-to-work / job-preferences field, never the headline. The mistake to avoid: a target title in the headline and then absent from open-to-work. This is the honesty gate applied to the profile surface.

Trace every line to evidence. No proposed bullet, metric, or claim without a backing evidence.yml claim. Honesty caps from honesty.yml apply (nothing in do_not_claim / do_not_fabricate; education policy honored). Profile copy is held to the same standard as a tailored résumé.

A flagged claim may be a stale evidence bank, not an overclaim. When a claim already on the profile does NOT trace to evidence.yml, do not default to softening or cutting it — the evidence bank may simply be out of date (a repo gained stars, a feature, or more pulls since the claim was last written). Verify the specific against the source first: the candidate's own repo (~/code/<repo>, gh repo view <owner>/<repo>) or their public portfolio. If it verifies, the fix is to update evidence.yml — record the grounded fact plus a dated source note — so the honest copy is backed, NOT to weaken it. Only if it cannot be substantiated do you soften or cut. Confirm with the candidate either way. (The firewall flags for human review; it does not auto-delete honest work.)

Expired-but-earned credentials are factual. A lapsed license or certification is still something the candidate genuinely earned — listing it is honest. The only dishonest move is hiding or forward-dating the expiry to imply current validity. Surface an expired credential as the candidate's choice — keep it shown-as-expired (transparent), recertify, or remove — never silently make it read as current.

Obfuscation / NDA convention. Internal employer project names are typically NDA and must not be made public. The source of truth for what is already public is the candidate's own public portfolio / OSS surface — repos and products they ship under their own name keep their real names and real public stats (stars, listings, downloads). On LinkedIn the employer is usually named, so describe internal employer work generically (no internal codenames, no project names) with sensitive specifics redacted. Keep only safe, already-public metrics; drop dollar figures, exact internal headcounts/record counts, vendor/system lists, and internal tool/channel counts. The current_base privacy invariant holds on everything.

Don't "fix" intentional styling. A candidate's deliberate brand styling in the headline (unusual separators, lowercase, symbols that match their portfolio) is a choice, not broken markup. Never flag stylistic choices as bugs.

Voice + length. Keep the About short and in the candidate's register — no corporate wall-of-bullets. Match writing-style.md.

No placeholder brackets. If a detail is unknown, go generic or omit it — never emit [Company], [Role], [Name], or any bracket token (the repo lint rejects them).

Write the full set to a document the candidate can act on, in the loose _linkedin/ bucket (per the Artifact Contract — a profile doc isn't a per-company round), e.g. ~/Documents/careerrat/_linkedin/linkedin-optimize-<yyyy-mm-dd>.md, formatted current → proposed per surface, with a one-line rationale and the backing evidence claim id for each change. Present the same diff inline to the user.

Record the batch for in-app review (same pass, after the doc): persist the same per-surface diff as a pending proposal batch so the Ask workspace can render it for per-field approval:

careerrat data linkedin-proposals record --data '<json>' --write

where <json> is { "surfaces": [{ "surfaceId", "surface", "current", "proposed", "rationale", "evidenceRef" }] }. The verb scans the entire batch payload, every surface field and property, not just proposed/rationale, and refuses the whole batch on any current_base/comp guard hit anywhere in it — fix the offending surface and re-record. This closes the Artifact Contract's two-places rule: the Documents doc plus this on-record batch.

If profile_apply is off, you are done here — the document plus the recorded batch is the deliverable. The user reviews the recorded suggestions in the app or applies edits by hand. Do not push to write-back.

STEP 5 — APPLY THE APPROVED EDITS (only if profile_apply is allowed)

Only with profile_apply allowed, and only after the user approves specific fields. This is confirm-first PER FIELD, never bulk-auto: show the before→after for one surface, get an explicit yes, write that one field, verify it, then move to the next. Nothing is written silently.

If the in-app pass already staged fields, read the batch first (careerrat data linkedin-proposals latest --status reviewed, or the default pending when some are held) and take its approved surfaces as the work-list. Staging supplies what to apply, never permission to skip the live confirmation. Still show each before→after and get a live yes here before writing that field, exactly as above.

Drive each surface live (snapshot/read before each action). Session-browser mechanics that the live run proved out — apply them tool-agnostically (describe the technique, never name a specific automation tool):

  • Find fields by their accessible label, not by coordinate scroll. Edit modals often open scrolled to the bottom; scrolling by fixed offsets snaps past the target field. Use the browser's element-finder to locate the field (e.g. the Description textarea) by its label/role and act on it directly.
  • Clear then type: focus the field, select-all, delete, then type the approved text.
  • If typing drops characters (common on React-controlled inputs — you'll see mangled output like dropped or merged characters): set the field's value via an injected script using the element's native value setter and dispatch an input event (and a change event) so the framework registers it, then re-read the field to confirm the clean value landed. This is the reliable fallback when direct typing corrupts the text.
  • After Save, skip any upsell the platform shows (e.g. a "people you may know" / connect prompt) — dismiss it, never take the upsell action.
  • Verify by reloading and re-reading. List/summary views go stale after an edit; reload the page and read the field text back to confirm the change is live before reporting it done.
  • Featured items are link cards. External links auto-pull a title/description that are then editable in the item's media-edit modal; edit the title/description there.
  • Contact info supports multiple websites, each with a URL and a type/label; the email field is typically read-only (managed in account settings) — don't try to edit it here.
  • Open-to-work / job-preferences title field is free-text and visible only to the user; editing the title can silently flip the job-alerts toggle, so re-check/restore that toggle after.

If a field's edit UI fights the automation, stop forcing it — hand that one field back as a suggestion (it stays in the STEP 4 document) rather than risk a bad write. The same honesty + current_base privacy invariants hold on everything written.

Never edit anything other than the candidate's own profile. This skill sends no messages, no connection requests, no reactions.

STEP 6 — VERIFY + LOG

For every field written in STEP 5, reload and re-read it live to confirm the new text is present and correct (quote it back to the user). The before→after document from STEP 4 stays in ~/Documents/careerrat/ as the record of the pass — write it regardless of whether anything was applied.

If the field was not already approved in the app, record the user's live yes first (careerrat data linkedin-proposals approve --batch <id> --surface <surfaceId> --version <n>); mark-applied only works on an approved surface.

For each field verified live, flip its batch decision to applied so the in-app card stops showing it as approved-but-unwritten:

careerrat data linkedin-proposals mark-applied --batch <id> --surface <surfaceId> --version <n>

Only mark a field applied after the reload-and-re-read confirms it — never on the write attempt alone.

Every decision bumps the batch version, and the command above prints the updated batch in its response. Each subsequent mark-applied call must use the version from the PREVIOUS call's output, never the original batch's version — re-read the current version via careerrat data linkedin-proposals latest --status any if you lost track of it.

Then log one event to the Activity Pulse feed (the dashboard's live timeline — see Activity Pulse in AGENTS.md), actor agent:

careerrat activity append --type system --actor agent \
  --title "LinkedIn profile pass" \
  --summary "<N surfaces reviewed, M fields applied (or 'suggest-only')>" \
  --url "<profile url>" --write

If the pass surfaces nothing the candidate should act on, say so — don't manufacture changes. The point is an honest profile that reads for the target roles, not a longer one.


RULES

  • On by default, two separate switches. Run a browser pass only where careerrat automation status --json shows the capability allowed: true for linkedin. profile_optimize covers reading + suggesting; profile_apply separately covers write-back. The allowed field is the mayRun() verdict from src/core/automation/consent.mjs (neither switch is turned off); never re-derive it in prose. If a switch is off, say so with the enable command and skip that part.
  • Dry-run preview is the default and the first move. Always produce the full read-only before→after set before writing anything. Applying is a deliberate, per-field second step. The skill is a complete read-only audit on its own.
  • Confirm-first per field on write. Editing a public profile = modifying public-facing content. Show the before→after for each field and wait for an explicit yes before writing it. Never bulk-auto. If a field's edit UI fights, hand it back as a suggestion.
  • Honesty firewall (hard). Every proposed or written line traces to an evidence.yml claim; honesty.yml overclaim caps and do_not_claim / do_not_fabricate apply; no fabrication. Profile copy is held to the same standard as a tailored résumé. The honest-vs-target field rule: honest descriptions in headline/About/experience, target titles only in open-to-work.
  • Stale evidence ≠ overclaim. A profile claim that doesn't trace to evidence.yml may mean the evidence bank is out of date, not that the profile overclaims. Verify against the source repo/portfolio first; if it's true, update evidence.yml rather than cutting honest copy. An expired-but-earned credential is factual — keep it shown honestly (as expired), never forward-dated to look current.
  • Obfuscation / NDA. Internal employer projects are described generically (no codenames, no project names), sensitive specifics redacted, only safe public metrics kept. The candidate's own public portfolio / OSS work keeps its real names + real public stats. Source of truth for "what's already public" is the candidate's own surface, never a guess.
  • Privacy invariant (hard). compensation.current_base must never appear in any suggested or written profile copy, or in any artifact under workspace/ / ~/Documents/careerrat/. Outbound-safe comp fields are expected_base, target_base, and minimum_base only.
  • Don't "fix" intentional styling. A candidate's deliberate brand styling is a choice, not a bug — never flag it.
  • Halt on auth challenges. On captcha, 2FA, login wall, or any unexpected interstitial, halt and ask the user to resolve it. Never bypass or automate around an auth challenge.
  • Tool-agnostic browser prose. Say "the session browser," "the browser's element-finder," "an injected script." Keep the provider on auto; never ask the user to choose a CLI, extension, or browser driver, and never name an MCP namespace or vendor tool.
  • Local-only. Scraped profile text, screenshots, and the before→after document stay under workspace/ or ~/Documents/careerrat/. Nothing goes outbound. No credentials are stored — the session browser holds the login.
  • Read-only on everything but the candidate's own profile fields. No messages, no connection requests, no reactions, no edits to anyone else's content.
  • No placeholder brackets. If a detail is unknown, go generic or omit it — never emit [Company], [Role], [Name], or any bracket token anywhere.
  • Domain-neutral. No hardcoded companies, roles, titles, codenames, or candidate-specific values in this skill. The skill is a field-agnostic procedure; what makes it conform to this candidate is their candidate/*.yml truth bank.

In-app and terminal paths

CareerRat's in-app path reads the candidate's LinkedIn profile through its owned browser workflow unless profile_optimize is turned off, then creates a bounded, evidence-grounded proposal batch for review. A terminal or external-agent run still follows STEP 0 onward exactly as written.

  • Requesting a profile pass (linkedin.optimize-request). "Optimize my LinkedIn" offers an "Optimize LinkedIn profile" chip. With profile_optimize turned off, the handler says so, offers a "Turn it on" action, and leaves any existing proposal batch visible. When reading is allowed, it runs the native profile pass and renders the returned proposal batch for review. Pasted profile text remains available on the terminal or external-agent path.
  • Staged suggestions (linkedin.proposal-decide). The in-app pass stages every suggestion that passes the checks on its own and saves it with an Undo. Each one shows as "Staged for your profile". A suggestion with a placeholder to fill in, or no evidence behind it, stays undecided for you to approve or reject. Undo returns a staged suggestion to undecided, and it refuses once the field was written to LinkedIn. A stale click (the batch changed underneath) refuses. Once every suggestion is decided the batch is reviewed, so read it with linkedin-proposals latest --status reviewed as well as the default pending.
  • Write boundary. Staging a suggestion in the app is local and automatic; no live LinkedIn field changes when it is staged. Applying approved edits live remains behind the separate profile_apply switch and a per-field confirmation on the terminal or external-agent STEP 5 path. STEP 6 verifies the live result and marks only verified fields applied so the card stays honest.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.