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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Changelog

## Unreleased
## 0.13.2 - 2026-08-23

- Publish exact Threads text through `posts.publish@5` without requiring a
PNG, while preserving optional PNG publication and exact permalink readback.
- Restore exact LinkedIn personal and organization profile reads from a
path-backed signed-in Chrome realm. Wrench clones the dormant profile into a
private contained session, binds the current member before either target
read, keeps personal profile and connection reads sequential, and finalizes
the browser's private artifacts after every result.

## 0.13.1 - 2026-08-23

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ The skill teaches Codex, Claude Code, Cursor, and other compatible coding
agents when to use Wrench, how to preserve its trust boundaries, and how to
install the CLI if it is missing. Start a new agent session after installation.

Install the current immutable CLI release from the `v0.13.1` tag:
Install the current immutable CLI release from the `v0.13.2` tag:

```sh
bun add --global github:hraness/wrench#v0.13.1
bun add --global github:hraness/wrench#v0.13.2
wrench adapter sync-bundled --json
wrench doctor
```
Expand All @@ -102,7 +102,7 @@ Install Wrench in an agent or application that owns its own model, planning,
tool loop, approvals, and interface:

```sh
bun add github:hraness/wrench#v0.13.1
bun add github:hraness/wrench#v0.13.2
```

```ts
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hraness/wrench",
"version": "0.13.1",
"version": "0.13.2",
"description": "Open-source CLI and TypeScript SDK for precise web capabilities for AI agents: page capture, verified media archives, encrypted reads, and typed provider operations.",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -110,6 +110,7 @@
"src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json",
"src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json",
"src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json",
"src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json",
"src/assets/adapters/reddit/wrench-web-adapter.json",
"src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json",
"src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json",
Expand Down
4 changes: 2 additions & 2 deletions skills/wrench/references/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ When the user asked to install or use Wrench, install the current immutable
release and its reviewed bundled adapter manifests:

```sh
bun add --global github:hraness/wrench#v0.13.1
bun add --global github:hraness/wrench#v0.13.2
wrench adapter sync-bundled --json
wrench --help
wrench doctor --json
Expand All @@ -31,7 +31,7 @@ Do not clone the repository merely to run the CLI. Importing the SDK is a
separate project dependency and does not install a global command:

```sh
bun add github:hraness/wrench#v0.13.1
bun add github:hraness/wrench#v0.13.2
```

## Add only required optional tools
Expand Down
116 changes: 83 additions & 33 deletions skills/wrench/references/linkedin-adapter.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# LinkedIn authenticated web API adapter

The current `linkedin-web` schema-v4 adapter has two observed operations:
`articles.draft.save@7` and `posts.publish@3`. The draft operation creates or replaces one private native Article
The current `linkedin-web` schema-v4 adapter has four observed operations:
`profiles.read@1`, `organizations.read@1`, `articles.draft.save@7`, and
`posts.publish@3`. The two profile-stat operations bind the current member,
then read one exact self profile or requested organization Page without DOM
automation. The draft operation creates or replaces one private native Article
draft for the bound current member, supports paragraphs, H1/H2 headings, native blockquotes,
native HTTPS links, one distinct banner cover, and ordered inline images with
required alt text and optional captions, and independently verifies the exact unpublished result
Expand Down Expand Up @@ -50,43 +53,70 @@ The separate `linkedin` adapter uses LinkedIn's documented OAuth API for approve

## Configure the signed-in realm

Prefer target-filtered Arc or Chrome cookies:
LinkedIn currently rejects exported session cookies outside their browser
context. Configure profile statistics with a path-backed, dormant Chrome
profile and the exact Chrome executable. A filtered cookie source may overlay
fresh origin-scoped cookies when the source browser is running:

```sh
wrench auth add linkedin-main --cookie-source arc --cookie-profile "Profile 2"
wrench auth add linkedin-main \
--browser-profile /absolute/private/chrome-snapshot/Default \
--browser-executable "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--trust-profile-egress \
--cookie-source chrome \
--cookie-profile "Profile 2"
wrench auth bind linkedin-main --site linkedin
wrench auth list --json
```

Use a private profile snapshot only when current browser storage or first-party assets are required for bootstrap. Never attach the runtime operation to a live inbox tab and never copy LinkedIn session material into an OAuth token document.

Bind `linkedin-main` to one stable current member/person identity before private reads or mutations. If organization operations are later added, bind the selected organization actor separately and prove that the current member can act for it. Reject account ambiguity, login changes, request-actor mismatch, and response-actor mismatch before dispatch.
The profile path grants its contained page scripts unfiltered public-host
egress and may contain every signed-in session in that browser profile. Keep it
private, outside the repository, and use it only with the user's informed
authority. Wrench refuses to clone an active Chromium user-data root. For a
scheduled realm, first load LinkedIn normally in the selected source profile
and confirm that its first-party session is current. Fully quit Chrome, take
one private snapshot, then point the auth locator at that dormant snapshot and
use the filtered cookie overlay for current session values. The overlay can
refresh valid cookies, but it cannot revive a source session that LinkedIn
already rejects with 401. Never attach the runtime operation to a live inbox
tab or copy LinkedIn session material into an OAuth token document.

Bind `linkedin-main` to one stable current member/person identity before private
reads or mutations. The observed organization Page read remains a
member-authenticated view and does not select an organization actor. A future
organization mutation must bind that actor separately and prove that the
current member can act for it. Reject account ambiguity, login changes,
request-actor mismatch, and response-actor mismatch before dispatch.

The retained candidate parser derives a mailbox only when the current-account
response directly names one bounded `miniProfile` reference and exactly one
included entity binds that reference to the same numeric member subject. It
does not scan other included profile entities as a fallback. Missing,
unbound, conflicting, or ambiguous direct bindings fail.

The client can strictly review a short-lived `__cf_bm` edge-cookie
The standalone client can strictly review a short-lived `__cf_bm` edge-cookie
rotation: it accepts only that name, validates origin, attributes, expiry, and
deletion semantics, and binds the encrypted cache to the auth-locator hash.
Removing the auth locator removes that cache. Only the observed Article draft
operation may cross the execution boundary; every capture-required operation
still refuses before this client is created.
Removing the auth locator removes that cache. Profile-backed statistics do not
export their session into that standalone client. Only the four observed
operations may cross the execution boundary; every capture-required operation
still refuses before its provider client is created.

Verification on July 23, 2026 produced a durable projection-drift failure and
every available LinkedIn realm returned `401` at current-account preflight.
Reauthentication alone does not re-promote the contract; a fresh low-stakes
capture must also prove the current identity, mailbox, query, response
That remains negative evidence for the independently capture-required inbox
contracts, not a demotion of the separately observed profile and Page reads.
Reauthentication alone does not promote an inbox contract; a fresh low-stakes
capture must also prove its current identity, mailbox, query, response
projection, and completeness semantics.

## Recapture inbox listing

`messaging.list` is a capture-required reservation. Version 1.1.0 remains
archived as historical evidence of the formerly observed bundle; version
1.2.0 first demoted it, and the current 1.7.0 bundle remains capture-required
and cannot execute. The intended folder input still reserves:
1.2.0 first demoted it, and version 1.7.0 preserves that unavailable contract
history. The current bundle remains capture-required and cannot execute. The
intended folder input still reserves:

- `focused` for the main inbox;
- `other` for the additional inbox;
Expand Down Expand Up @@ -117,23 +147,43 @@ use only a reviewed message query and must not mark it read.

## Profiles, organization Pages, and connections

The adapter reserves four bounded semantic operations without claiming that
their internal requests are known:

- `profiles.read` selects one exact public profile identifier or provider
profile URN;
- `organizations.read` selects one exact organization public identifier or
organization URN and means viewing that LinkedIn Page, not acting as it;
- `relationships.recommendations.read` selects the `all` recommended-connections
surface and one bounded page;
- `relationships.connect` sends one confirmed invitation to an exact profile
URN, with an optional note of at most 300 characters.

All four are `capture-required`. The three reads are R1; the invitation is R3
with a 24-hour local-at-most-once window. Their presence in `wrench
capabilities` makes the CLI shape reviewable and stable while guaranteeing that
no request runs before a managed HAR proves the exact request, viewer scope,
target, response, paging, completeness, and duplicate-state behavior.
`profiles.read@1` is an observed R1 read for one exact signed-in self profile
URL. It binds the current member to the requested public slug and projects the
exact follower count. When `include_connections` is true, it also reads the
exact private My Network connection total in the same ordered browser
transport. `organizations.read@1` is an observed R1 read for one exact company
Page URL. It binds the requested company record and its matching following
state before projecting the exact follower count. Viewing a Page does not
confer Page-actor authority.

For a path-backed auth realm, Wrench starts a task-private contained clone and
uses the browser path before any standalone cookie preflight. One code-owned
evaluation derives the reviewed CSRF header from the unique `JSESSIONID` and
performs exact `GET /voyager/api/me`. After binding that response to the auth
subject and requested self slug, it reads the exact profile response and, when
requested, the private connections response in order. The organization branch
instead reads only the exact requested Page after the same member binding.
Every response is bounded, same-origin, exact-route, status- and media-type
checked before projection. Login, checkpoint, redirect, network, cookie,
identity, target, or response drift fails closed. Wrench does not click or
inspect LinkedIn DOM, expose a caller-selected selector or script, follow a
redirect, or retry after an arbitrary browser failure. It closes the contained
browser and verifies cleanup after success or failure.

Live evidence on August 23, 2026 showed that LinkedIn returned deletion
cookies for a valid `li_at` when the same Chrome session was replayed through a
standalone client, even with Chrome's normal request headers. A cookie-only
realm is therefore not the scheduled profile-stat transport. The path-backed
browser branch preserves the browser/device context while still keeping the
operation fixed and target-bound.

`relationships.recommendations.read` and `relationships.connect` remain
`capture-required`. The former reserves one bounded R1 page from the `all`
recommended-connections surface. The latter reserves one R3 invitation to an
exact profile URN, with an optional note of at most 300 characters and a
24-hour local-at-most-once window. Neither performs a request before a managed
HAR proves its exact request, viewer scope, target, response, paging,
completeness, and duplicate-state behavior.

Do not treat `organizations.read` as organization-actor authority. A future
Page-authored post or comment must separately bind the current member, selected
Expand Down Expand Up @@ -251,7 +301,7 @@ Keep query IDs, CSRF material, cookies, variables, feature sets, and private ide

The current registry keeps these unavailable until their exact first-party exchanges are captured and reviewed:

- `feeds.read`, `profiles.read`, `organizations.read`, `relationships.recommendations.read`, `messaging.list`, `messaging.read`, `posts.read`, `comments.read`, and `articles.read` (`R1`);
- `feeds.read`, `relationships.recommendations.read`, `messaging.list`, `messaging.read`, `posts.read`, `comments.read`, and `articles.read` (`R1`);
- `messaging.send` (`R3`);
- `posts.repost` and `posts.quote` (`R3`);
- `comments.create` and `replies.create` (`R3`);
Expand Down
6 changes: 3 additions & 3 deletions skills/wrench/references/platform-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ Leave the state `capture-required` when current evidence is absent, ambiguous, e
## LinkedIn

- Use `linkedin-web` with a browser-session/cookie realm for consumer Home feed, inbox folders, conversations, and native article-editor surfaces. The separate official `linkedin` OAuth adapter covers approved post, comment, reply, repost, and reaction scopes; it does not supply the consumer Home feed or inbox.
- Current bundle 1.16.0 observes `linkedin-web articles.draft.save@7` and image-only `posts.publish@3`. The Article contract binds the numeric member subject to the normalized Article-author profile, creates or replaces one exact private draft, keeps a supplied cover in the Article banner rather than the body, and can preserve the independently read existing banner during an exact replacement without another cover upload. It supports paragraphs/H1/H2/native blockquotes, native HTTPS links, and 1–20 bounded inline JPEG/PNG/WebP images with required alt text and optional captions, and verifies the exact unpublished editor-response readback. Its fixed cover and inline-image single-upload registrations, byte transfers, autosaves, and readbacks run inside contained Chrome without DOM automation. The separate post contract stages a real-size optional PNG through bounded ordered commands, admits one exact image transfer and post create, durably retains the accepted share target, and independently verifies it. `linkedin-web media.publish@1` separately reserves an MP4 route but remains capture-required; the official `linkedin posts.publish` OAuth contract already observes MP4.
- Every other LinkedIn web operation remains capture-required. Explicit inert reservations cover inbox folders, one member profile, one organization Page, one bounded page of recommended connections, one connection invitation, Article reads/publication, and other comment/message/repost surfaces. Their retained candidates do not confer executable internal requests.
- Current bundle 1.18.0 observes `linkedin-web profiles.read@1`, `organizations.read@1`, `articles.draft.save@7`, and image-only `posts.publish@3`. The two profile-stat reads use a path-backed contained Chrome realm, bind the current member before projecting one exact self profile or requested organization Page, and keep the optional private connection read sequential with the self-profile read. The fixed browser evaluation performs only bounded, exact-route first-party fetches; it does not click or inspect LinkedIn DOM. A cookie-only standalone client is not a fallback because current live evidence shows LinkedIn invalidates an exported `li_at` outside its browser/device context. The Article contract binds the numeric member subject to the normalized Article-author profile, creates or replaces one exact private draft, keeps a supplied cover in the Article banner rather than the body, and can preserve the independently read existing banner during an exact replacement without another cover upload. It supports paragraphs/H1/H2/native blockquotes, native HTTPS links, and 1–20 bounded inline JPEG/PNG/WebP images with required alt text and optional captions, and verifies the exact unpublished editor-response readback. Its fixed cover and inline-image single-upload registrations, byte transfers, autosaves, and readbacks run inside contained Chrome without DOM automation. The separate post contract stages a real-size optional PNG through bounded ordered commands, admits one exact image transfer and post create, durably retains the accepted share target, and independently verifies it. `linkedin-web media.publish@1` separately reserves an MP4 route but remains capture-required; the official `linkedin posts.publish` OAuth contract already observes MP4.
- Every other LinkedIn web operation remains capture-required. Explicit inert reservations cover inbox folders, one bounded page of recommended connections, one connection invitation, Article reads/publication, and other comment/message/repost surfaces. Their retained candidates do not confer executable internal requests.
- Exclude presence, messaging badges, delivery acknowledgements, seen/read receipts, and notification badge traffic from every R1 contract.
- Bind the current viewer's person/member identity to the auth realm. `organizations.read` views a Page and does not confer Page-actor authority. For organization actions, additionally bind the administered organization actor selected by the plan.
- Keep `articles.publish` separate as R3. Messages, posts, comments, replies, reposts, quotes, connection requests, and native article publication are also R3. Treat reversible reaction/follow/save desired state as R2 only after both create and delete exchanges are captured.
- Keep `articles.publish`, `messaging.send`, `relationships.connect`, post/comment mutations, and reactions `capture-required` until their exact request, response, actor, target, and readback contracts pass a low-stakes fixture. `comments.create` targets a post rather than a profile/Page itself. Never send through a textbox or editor fallback.
- An authenticated dedicated Chrome realm passed the current-member and exact private Article editor-response readback on August 16, 2026. LinkedIn still binds this family to the browser/device session; a cookie-only standalone client is not an allowed fallback. Inbox operations remain independently capture-required.
- An authenticated dedicated Chrome realm passed the current-member and exact private Article editor-response readback on August 16, 2026. On August 23, an exact personal profile and connection read also proved that a current normal first-party source session plus a dormant snapshot and filtered cookie overlay succeeds, while the same overlay cannot revive a source session already returning 401. LinkedIn still binds this family to the browser/device session; a cookie-only standalone client is not an allowed fallback. Inbox operations remain independently capture-required.
- Avoid employment and recruiting threads as fixtures.

Read [linkedin-adapter.md](linkedin-adapter.md) before changing the LinkedIn registry.
Expand Down
Loading