Install
openclaw skills install @umerkha2007/linkedin-easy-applyLinkedIn job-sprint automation — verify/refresh login in the dedicated Playwright profile, then run the Easy Apply automation. Use when resuming or starting a LinkedIn application sprint.
openclaw skills install @umerkha2007/linkedin-easy-applyAutomates a LinkedIn Easy Apply job-application sprint end to end: verifies (or refreshes) login in a dedicated Playwright/Chrome profile, searches LinkedIn by your criteria, fills and submits Easy Apply forms — answering open-ended questions with an LLM grounded in your resume — logs every outcome so re-runs never double-apply, and optionally sends connection requests to each job's hiring team after a successful submission.
Use it whenever the user asks to resume, start, or check status of a LinkedIn application sprint, or to verify/refresh the automation's LinkedIn login. Do not attempt to reimplement this workflow (browser automation, selector logic, application dedup, daily-limit detection) yourself — always invoke the installed runtime described below. This skill's package intentionally ships no implementation code; the working automation lives in a separately installed Python runtime, and reproducing its logic ad hoc will not match its tested behavior (dedup rules, retry rules, daily-limit handling, etc.).
This skill's package ({baseDir}) contains no automation code — only this file and
{baseDir}/references/README.md. The actual automation is a separately distributed Python
runtime, set up ahead of time by the user via the steps in that reference file. Locate it
in this order:
$LINKEDIN_AUTOMATION_RUNTIME_DIR, if set.~/.openclaw/workspace/linkedin-automation-runtime/.A cli.py file existing at a candidate location is a shape check, not authorization to run
it. Before invoking anything, perform both of these checks at that location, every time —
not just once per session:
VERSION next to cli.py and compare it to expectedRuntimeVersion in this file's
frontmatter (1.0.11).cli.py itself and compare it to expectedCliSha256 in this
file's frontmatter (b7ce43afce06e31669e6518b856bdb807887e3ba41d32bb975e8e0408eb8ce4d) — not to any file sitting next to cli.py.
A hash generated from whatever cli.py happens to be present at install time proves
nothing (an attacker who supplies a malicious cli.py before that baseline is created
can supply a matching sidecar hash too); expectedCliSha256 is instead computed once at
build time from the reviewed source and embedded in this file, so it comes from the same
independently-published, reviewed channel as expectedRuntimeSha256 below, not from the
runtime directory being validated..pyc files to guess compatibility. Tell them either to
install the version this skill expects ({baseDir}/references/README.md, including its
checksum-verification step) or that this skill needs to be rebuilt/updated to declare
support for the runtime version they have installed.Note what each check does and doesn't prove: VERSION is a plaintext file the installed
runtime reports about itself — a match proves it claims to be the expected release, not
that its contents are what was published. expectedCliSha256 re-verifies cli.py (the file
this skill directly executes) against a value pinned in the reviewed skill package itself,
on every invocation, not just once at install time — so a cli.py modified after install
(by malware, a faulty update, etc.) is caught before it runs, and the expected value can't be
forged by whoever controls the runtime directory. It still only covers cli.py, not every
module/dependency the runtime imports; the archive-level integrity guarantee for the full
runtime tree is expectedRuntimeSha256 in this file's frontmatter (289e1abf49a1478d26b457c79fa0dff9d4b43b9cb9fce4054a6ac06a3de6e16e),
checked once by the user at install time per {baseDir}/references/README.md against the
downloaded archive — compared there against two independent copies of the digest (the
.sha256 file published next to the archive, and this value embedded in SKILL.md, which is
reviewed and distributed through a separate channel). If a user reports installing a runtime
whose archive digest didn't match either published value, treat that as a
compromised-supply-chain report, not a support request — stop and tell them not to proceed,
don't troubleshoot around it.
If no runtime is found at either candidate location at all, stop and tell the user.
Point them at {baseDir}/references/README.md for setup, and at
LINKEDIN_AUTOMATION_RUNTIME_DIR to tell this skill where they put it (or leave it at the
default path above). Do not substitute your own approach for locating, obtaining, or
standing up the runtime — this skill only ever drives an already-prepared, version-matched
installation.
Once located, every command below is: python "<runtime_dir>/cli.py" <subcommand> [args].
All inputs are environment variables — see the envVars list in this file's frontmatter
for the full set. ANTHROPIC_API_KEY is required; everything else has a working default.
Check login (fast, no browser):
python "<runtime_dir>/cli.py" login-check
Output: prints exactly logged_in or needs_login on stdout.
If needs_login, log in interactively:
python "<runtime_dir>/cli.py" login
Opens a real Chrome window on the automation profile and waits (up to 5 minutes) for
the user to finish logging in (including 2FA). Tell the user the window is open and
wait for the process to exit — do not poll or interrupt it.
Verify:
python "<runtime_dir>/cli.py" verify
Output: prints verified on success, or not_verified url=... title=... on failure.
Smoke test (real browser, submits nothing):
python "<runtime_dir>/cli.py" smoke
Run before trusting a real sprint. Prints progress lines ending in either
SMOKE TEST PASSED — ... or an AssertionError describing what failed (not logged in,
or the scraper found zero candidates).
Run a sprint:
This submits real job applications (and, unless AUTO_CONNECT_HIRING_TEAM=false, sends
real connection requests) on the user's behalf using an externally distributed runtime
this skill package does not contain the source of — get explicit confirmation from the
user for this specific invocation before running it (not a one-time blanket "yes" earlier
in the conversation), stating how many applications (N) will be submitted.
python "<runtime_dir>/cli.py" run [N] [--scan-per-page N] [--max-scan N] [--force]
N is the number of applications to submit this call (default 1), checked against
today's successful submissions only (not a lifetime total) — pass --force to run
anyway if today's target already looks reached. Invoke once per call — this automation
schedules itself externally at randomized intervals and must not be looped internally,
to avoid LinkedIn bot-detection.
Every subcommand prints human-readable progress lines to stdout and exits 0 on success,
non-zero on failure (an unhandled exception, or an AssertionError from smoke). There is
no structured JSON output — read the printed lines directly. run additionally persists
every outcome (submitted / skipped / already-applied / blocked) to the JSON log at
$APPLICATION_LOG_PATH, which downstream tooling can read for a structured summary.
NOT LOGGED IN, redirected to /login) → session
expired; run login again, then retry.smoke fails with zero candidates found → possible stale LinkedIn selectors on the
runtime's end; this needs a runtime rebuild/update, not a workaround here — report it to
the user rather than trying to patch around it.run stops cleanly citing a daily submission limit → expected LinkedIn behavior, not an
error; tell the user to resume tomorrow.