Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Download from the [Releases](https://github.com/yashiels/linkedin-cli/releases)
lnk auth login # paste your li_at cookie
lnk search "software engineer" --location "Cape Town" --easy-apply
lnk job 4414623196 # view full job details
lnk job 4414623196 --check-apply # verify the current application route
lnk apply 4414623196 --dry-run # preview Easy Apply
lnk profile johndoe # view any profile
lnk feed --limit 10 # recommended jobs
Expand Down Expand Up @@ -92,20 +93,58 @@ lnk job <job-id> [flags]
| Flag | Description |
|---|---|
| `--open` | Open the job URL in your default browser |
| `--check-apply` | Verify the current application route; may make bounded anonymous GETs to a supported employer ATS URL |

**Example:**

```bash
lnk job 4414623196
lnk job 4414623196 --open
lnk job 4414623196 --json
lnk job 4414623196 --check-apply --json
```

Job details include an `application` object while preserving the existing JSON
fields. Its status is deliberately conservative:

| Status | Meaning |
|---|---|
| `accepting` | LinkedIn returned `LISTED`, `onsiteApply: true`, and the exact Easy Apply CTA; or supported employer proof was verified |
| `closed` | LinkedIn returns `CLOSED`/`SUSPENDED`, or Workday JSON returns `jobPostingInfo.canApply: false` |
| `unverified` | No supported proof was available, including missing controls, unsupported ATS destinations, network failures, and login or challenge pages |

`expired: false`, `jobState: LISTED`, an HTTP 200 response, description text that
says “Apply,” or the presence of an employer URL does not mean a job is accepting
applications. `listingUrl` is the LinkedIn listing; `application.applyUrl` is an
employer application URL observed in LinkedIn data. They are never interchangeable.
For the same URL, blank ATS metadata does not replace a real provider. Conflicting
non-empty ATS names remain `unverified`. When LinkedIn returns multiple employer
URLs, one deterministic observed URL is retained without choosing a provider.

`--check-apply` supports narrow proof checks for Lever and Workday. Lever must
return the identified posting's POST application form with an active submission
control inside that form. The control and its ancestors must also be visible by
the supported HTML signals: `hidden`, `aria-hidden=true`, a `hidden` class, or
inline `display:none`/`visibility:hidden`. This is not a complete CSS layout
engine. Workday must return JSON containing a boolean
`jobPostingInfo.canApply`; a normal Workday HTML landing page alone remains
`unverified`. Other ATS destinations remain `unverified` and retain their observed
URL for manual review. Employer checks require HTTPS, use an anonymous client that
does not send LinkedIn credentials, reject non-public destinations and unsafe or
cross-posting redirects, allow at most three redirects, read at most 1 MiB, and
time out after eight seconds.

Lever text such as “deadline passed” or “no longer accepting” is not structured
closure proof. Without a valid active form, Lever remains `unverified`; agents must
check dated deadlines separately in the current description.

---

### apply

Apply to a job via Easy Apply.
Apply to a job via Easy Apply. Known-closed jobs are rejected before the Easy
Apply flow. If Easy Apply is unavailable, `lnk` reports an observed employer URL
as unverified or reports that none was observed; it does not generate an employer
URL from the LinkedIn listing.

```
lnk apply <job-id> [flags]
Expand All @@ -124,6 +163,19 @@ lnk apply 4414623196 # apply with confirmation prompt
lnk apply 4414623196 --confirm # apply without prompt
```

## Read-only job verification workflow

For job discovery or a daily scout:

1. Deduplicate search results by the stable job `id`, not title or URL.
2. Run `lnk job <job-id> --check-apply --json` for each candidate.
3. Re-read any closing date in the current description and treat a passed or ambiguous deadline as not apply-now, regardless of status.
4. Put only `application.status == "accepting"` jobs in an apply-now shortlist; keep `unverified` jobs in a separate manual-review list.
5. Assess suitability separately and state qualification or experience gaps explicitly. Application proof says nothing about candidate fit.

This workflow is read-only. Never run `lnk apply`, `lnk saved add/remove`, or
`lnk alerts create/delete` without explicit user consent for that exact write.

---

### saved
Expand Down
16 changes: 10 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ module github.com/yashiels/linkedin-cli
go 1.26.4

require (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/BurntSushi/toml v1.6.0
github.com/fatih/color v1.19.0
github.com/mattn/go-isatty v0.0.20
github.com/spf13/cobra v1.10.2
golang.org/x/net v0.59.0
golang.org/x/term v0.46.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/sys v0.48.0 // indirect
)
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Loading
Loading