Wrench

Wrench: precise web capabilities for AI agents

skills.sh

Give agents bounded access to pages, media, and connected accounts.

Wrench is an open-source, bring-your-own-agent CLI and TypeScript SDK. It is the capability and custody layer beneath any AI agent that can run a command: a way to capture pages, preserve media, query encrypted snapshots, and use reviewed account capabilities without handing the model a mouse, keyboard, cookie jar, arbitrary HTTP client, or every signed-in tab.

The caller asks for a named outcome such as messaging.list. Wrench binds that operation to one exact provider, transport, account realm, contract version, implementation, and risk level. If those facts drift, the operation stops. It does not silently fall back to general browser control.

Bring the model, planner, tool loop, approval interface, and application shell you prefer. Wrench supplies precise web capabilities with local custody and explicit evidence.

wrench https://example.com/article
wrench capabilities
wrench plugin list

Install · npm package · Project site · Privacy and data custody · Security policy · Plugin guide · Local CLI transport guide

What Wrench does

  • Capture knowledge. Turn a public URL into durable Markdown, inspect it without saving, and search the knowledge you keep locally.
  • Preserve media. Archive one authorized, accessible, finite media item with source bytes, requested derivatives, transcript, manifest, and SHA-256 integrity records.
  • Read connected services. Store validated account-bound reads as encrypted exact-query snapshots, then load the last verified state without reopening a browser or contacting the provider.
  • Add one capability. Turn a reviewed first-party exchange into a typed, semantic operation with strict inputs, bounded outputs, and explicit trust.

Built-in provider catalog

This v0.16.2 source tree defines actions for 19 services: Beeper, Bluesky, Facebook, Facebook Groups, Facebook Marketplace, GitHub, Gmail, Hacker News, Instagram, iMessage, LinkedIn, Reddit, Substack, Threads, TikTok, Twitch, WhatsApp, X, and YouTube. LinkedIn and X each have separate official and authenticated-web adapters. The release-bound provider directory lists only executable actions, grouped by the tasks each service supports and the access method each action uses. Inspect wrench capabilities --json for the exact installed contract state.

Beeper is Wrench's first pinned local-CLI provider. Its 32 supported actions read accounts, contacts, conversations, and messages; manage reactions, drafts, reminders, and conversation state; and preview and confirm sends, edits, group changes, and presence. Wrench accepts only the reviewed official Beeper CLI 0.6.2 executable and one bound Desktop target. It does not expose a generic command runner, and submission is not a claim of network delivery.

wrench messaging routes --input @/absolute/private/beeper-routes-request.json \
  --private-output /absolute/private/beeper-routes.json --json
wrench messaging resolve --input @/absolute/private/beeper-resolve-request.json \
  --private-output /absolute/private/beeper-route.json --json
wrench messaging context --input @/absolute/private/beeper-context-request.json \
  --private-output /absolute/private/beeper-context.json --json
wrench messaging preview --input @/absolute/private/beeper-turn.json \
  --private-output /absolute/private/beeper-preview.json --json

routes returns bounded discovery evidence and opaque candidate references. Put one candidate routeRef in the private resolve request. Wrench loads the checked provider target from encrypted private state and performs the exact provider read. The caller never resupplies an account, network, conversation ID, name, handle, title, or participant match. Preview is draft-only until the owner reviews the exact private recipient and bubbles and makes a fresh same-turn send request.

Read the focused Beeper guide for setup, version identities, action boundaries, export workflows, and exclusions.

Why Wrench is different

  • Intent over mechanism. Agents receive labeled operations, not credentials, selectors, scripts, caller-selected endpoints, or unrestricted browser access.
  • Exact identity. Authenticated calls bind the provider, origin, transport, account, contract, and implementation instead of relying on ambient state.
  • Visible drift. A changed origin, account proof, status, field, or response shape returns to capture-required rather than guessing or changing tools.
  • Local custody. Archives remain inspectable and exact provider snapshots remain encrypted. Verified cached reads can work without a provider roundtrip.
  • Honest mutations. Consequential writes require an exact preview and durable dispatch evidence. An indeterminate write is reconciled and never blindly retried.
  • Content-bound trust. Portable plugin approval applies to one verified content-addressed bundle, so changed code requires a new trust decision.

Wrench complements browser automation, direct API clients, MCP, and agent frameworks. Those tools own interfaces, transports, models, and planning. Wrench owns the narrow capability boundary that can sit beneath them.

Install

This README is bound to the package version in this source tree. Its exact npm, tag, and Agent Skill coordinates can become individually reachable while a release is being staged. Treat them as one completed, supported public release only after the workflow has verified the registry package and created the matching immutable GitHub Release. The release-bound production site identifies the latest release that completed every gate.

Install the single Wrench Agent Skill with either runner:

npx skills add hraness/wrench#v0.16.2
# or
bunx skills add hraness/wrench#v0.16.2

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.

After the matching immutable Release exists, install this exact version from npm:

bun add --global @hraness/[email protected]
wrench adapter sync-bundled --json
wrench doctor

Wrench requires Bun 1.3.14. It runs on macOS and Linux. wrench doctor reports capture, media, authentication, provider, plugin, and durable-recovery readiness. Provider-specific commands remain unavailable until their exact local dependency and auth contracts are ready.

wrench adapter sync-bundled atomically installs the reviewed data manifests shipped by that exact package version. It upgrades only an exact current or archived bundled baseline and preserves any independently modified install.

The public manifest projects each closure-attested package as an exact runtime dependency. Standalone validation installs without the repository lock, then verifies the resolved closure versions and reviewed entrypoint hashes.

SDK and code mode

For that same released coordinate, install Wrench in an agent or application that owns its own model, planning, tool loop, approvals, and interface:

bun add @hraness/[email protected]
import {
  isProviderPluginId,
  isProviderPluginOperationName,
  type ProviderPluginDefinitionV1,
} from "@hraness/wrench"

if (!isProviderPluginId(candidate.id)) {
  throw new Error("invalid plugin ID")
}

const plugin = candidate satisfies ProviderPluginDefinitionV1
void plugin

The package root exposes programmatic plugin types and bounded validators. @hraness/wrench/client exposes persistent-read and strict live-invocation helpers, @hraness/wrench/beeper exposes the body-free Beeper contact interaction export, and @hraness/wrench/omni exposes normalized cross-provider reads. Importing any SDK entrypoint does not start the CLI. Importing the package root also does not inspect local state or load provider runtimes.

Consumers that need one strictly parsed live result without cache orchestration can use the generic client directly:

import { invokeCapabilitySync } from "@hraness/wrench/client"

const result = invokeCapabilitySync({
  adapterId: "beeper-local",
  operationId: "contacts.list",
  authId: "beeper-main",
  input: { limit: 100 },
})

if (result.status === "failed") {
  handleReadFailure(result.readFailure)
} else {
  render(result.output)
}

The asynchronous invokeCapability form accepts an abort signal. Both forms run Wrench's execution and projection identity fences before and after the read, then return a discriminated result instead of asking a consumer to parse the raw process envelope. The receipt-bound top-level status narrows both branches in ordinary TypeScript control flow. Failed results carry one closed readFailure category and retry disposition. Consumers use that policy field for control flow and never inspect the receipt's bounded diagnostic text.

Capture and inspect

wrench URL                         # capture into a Markdown knowledge base
wrench read URL                    # inspect without persistence
wrench archive URL                 # create a verified media archive
wrench audio URL
wrench video URL
wrench transcript URL
wrench verify path/to/archive-item
wrench context path/to/code        # resolve nearby agent context
wrench search "query"              # search the local knowledge base
wrench url-metadata backfill --root kb
wrench doctor --json

Local browser admission

Wrench permits at most two locally owned browser acquisitions at once across all Wrench processes that share the same state home. This first gate covers fresh and profile-backed page capture. Explicit --cdp and --browser-live attachments do not launch a Wrench-owned browser and therefore do not consume a slot.

Admission is automatic. Polling uses bounded jitter and a budget equal to the lesser of the remaining capture timeout and 30 seconds. Queueing consumes the capture timeout. An in-flight bounded state-safety operation may settle after that polling budget expires, but Wrench rechecks the deadline and rolls back a late claim, so no browser launches after it. Each claim binds a random token to the owner's exact process-start identity. Wrench automatically reclaims a claim only after it verifies that the claim came from an earlier operating-system boot. A same-boot claim remains occupied even when its Wrench owner is dead because an owned agent-browser daemon or Chromium process may have survived. Malformed and unverifiable claims also remain occupied, so ambiguous state can reduce capacity but cannot raise it above two.

Initialize a brand-new state home once before starting several Wrench processes:

wrench runs list --json

If a crash leaves capacity blocked, run wrench doctor --json and read wrench.home from the report. The admission files are under <wrench.home>/captures/browser-admissions. Rebooting is the safest recovery; the next capture can verify the prior-boot claim and retire it. Manual recovery on the same boot requires first finding and terminating the exact orphaned agent-browser and Chromium process group, then removing only its corresponding slot-N.json. Never remove a claim merely because its Wrench PID is gone.

The slot remains held through upstream browser, proxy, process, and isolation cleanup settlement. Managed provider/bootstrap and derivation browser sessions remain outside this first gate and keep their existing containment and cleanup boundaries.

wrench url-metadata delegates to the shared @hraness/kb URL-intelligence boundary. Backfill searches for bounded metadata through its pinned Rust search helper, records resumable url-metadata.json sidecars beside saved URLs, and performs read-only Archive.today discovery, including archive.is URLs, by default. Pass --no-archive to disable archive discovery or --refresh to replace an existing sidecar after a fresh bounded lookup. Run wrench url-metadata --help for the complete limits and helper-path options.

Wrench archives one accessible, finite, non-DRM media item at a time. It rejects playlists, live streams, affirmative DRM, and unsupported authentication instead of weakening the archive boundary. Use it only for material you are authorized to access. Wrench does not bypass authentication, payment, access controls, or DRM.

Each completed media item retains the acquired encoded media, privacy-projected provider metadata, requested derivatives and transcripts, a versioned manifest, and SHA-256 integrity records. Inspect the directory directly and run wrench verify to recompute every recorded artifact hash.

Inspect provider support

wrench capabilities --json
wrench capabilities x-web --json
wrench plugin list --json
wrench plugin show x-web --json
wrench platforms --json
wrench plugin doctor --json

capabilities reports the installed semantic operations and their current contract state. A capture-required operation is an inert reservation, not a partially supported request. Source plugins are trusted in-process code. Portable plugins run as explicitly trusted child-process code; process separation contains ordinary failures but is not a hostile-code sandbox.

Persistent reads

Successful R1 invocations with a verified account subject publish an encrypted snapshot of the exact validated query and bounded provider output. The same query can be returned later without opening a browser or provider connection:

wrench auth bind reddit-main --site reddit
wrench reddit-web messaging.list --auth reddit-main --input '{"folder":"inbox","limit":25}' --json
wrench reddit-web messaging.list --auth reddit-main --input '{"folder":"inbox","limit":25}' --cache-only --json

Observed profiles.read capabilities expose target-bound exact counters for X, Bluesky, GitHub, LinkedIn, Instagram, Threads, Substack, YouTube, Twitch, Reddit, and TikTok; Substack also exposes owned-publication subscriber totals through organizations.read. Each counter is either an exact nonnegative integer or a categorical unavailable value. Wrench never promotes a rounded profile label to an exact metric. The Agent Skill includes the bounded daily collection and consumer-handoff workflow.

Normal invocation is the explicit revalidation step. Cache publication has a separate outcome from the live read, so a failed refresh or local publication never erases the last good snapshot. Inputs, account subjects, cursors, private IDs, and provider output remain inside authenticated local ciphertext. Replacing or removing an auth locator rotates its local lifetime identity, so old projection and provider-session ciphertext cannot revive after recreation.

For social video, inspect the exact installed schema before planning. Current source observes MP4 publication through x-web posts.publish, the official OAuth x and linkedin post contracts, reddit-web media.publish@9, and Threads meta-web media.publish@1, plus bluesky-web media.publish@2. Reddit's route requires one plan-bound MP4, one plan-bound PNG/JPEG poster, and explicit post declarations. The Threads route binds MP4 dimensions, upload, created identity, actor, text, and exact permalink video readback. The Bluesky route binds the fixed video-service upload job and processed blob to the exact repository record, durable target, and authoritative PDS plus public AppView readbacks. LinkedIn web, Substack Notes, TikTok, Instagram, and YouTube expose bounded media.publish reservations, but those routes remain network-inert while their provider-specific upload, processing, and independent readback contracts are capture-required. Substack's reservation now has live 200 evidence for initialization, multipart transfer, transcode, status, and video-attachment creation, but remains inert: the authorized profile-backed Note create returned 403 in two independent attempts, so no exact published-video target or readback exists.

reddit-web media.read@2 reads one exact Reddit-hosted video post through the current-account-bound /api/info exchange. It returns only stable post fields, dimensions, duration, safety flags, and completed-transcode status. Canonical, fallback, signed, and expiring playback URLs are deliberately excluded. Standalone Threads post and media reads and Facebook Marketplace media reads remain capture-required; an observed feed, listing, or publication contract does not confer those reads.

substack-web content.delete@1 is observed for one exact current-account personal Note. It pre-reads the exact actor and body, dispatches one bodyless target-bound DELETE, retains the accepted target, and independently requires the exact Note read to return 404.

UI clients can render the current snapshot before awaiting revalidation:

import { staleWhileRevalidateCapability } from "@hraness/wrench/client"

const messages = staleWhileRevalidateCapability({
  adapterId: "reddit-web",
  operationId: "messaging.list",
  authId: "reddit-main",
  input: { folder: "inbox", limit: 25 },
}, { freshForMs: 30_000 })

if (messages.cached?.status === "hit") {
  render(messages.cached.output, messages.cached.freshness)
}
const refreshed = await messages.revalidation
if (refreshed.current?.source === "cache") {
  render(refreshed.current.output, refreshed.current.freshness)
} else if (refreshed.current?.source === "live") {
  render(refreshed.current.output)
}

current applies Wrench's ordering policy. It prefers the verified cachedAfter snapshot after a failed refresh, a superseded publication, or a cache error with a concurrently advanced run, revision, or validation time. It uses live output only when that output is still current, and is null when a failed refresh has no last-good snapshot. cachedBefore, cachedAfter, live, and cache remain available for diagnostics and richer UI states.

Exact snapshots preserve provider page and completeness semantics without reinterpretation. Revalidation reruns the selected R1 operation; it does not imply a separate provider sync. In particular, WhatsApp reads revalidate its local linked-device projection, while wrench auth sync <id> --once remains explicit.

Contact providers

contacts.list uses one shared directional-statistics shape. A count is either complete, an explicit lower bound, or null when the provider cannot supply it. Timestamps carry the same completeness and basis evidence. Providers do not turn missing message history into zero activity.

Provider Contact collection Directional statistics
Gmail Google People connections Bounded Gmail message scans with explicit truncation
Beeper local Desktop One coverage-limited account-aware result window from the already-authorized local Desktop projection; the CLI exposes no continuation and may cap results below the requested limit Unavailable; Wrench does not scan message history while listing contacts
LinkedIn official API First-degree connections with locale-selection evidence Unavailable; the Connections API does not expose ordinary inbox history
Instagram authenticated web Unique non-viewer participants from the reviewed first Direct inbox summary page, with explicit first-page and pagination incompleteness Unavailable until acknowledgement-free message-history paging is reviewed
WhatsApp linked device One page of the authenticated account owner's private, quiescent Whatsmeow contact store Unavailable; Wrench does not treat a linked-device message cache as account-owned history
Facebook authenticated web Capture-required reservation for friends or Messenger participants Capture-required
Telegram Not installed Requires a reviewed TDLib user-session lifecycle; Wrench does not substitute the Bot API or claim contact access

LinkedIn requires approved access to both the restricted r_1st_connections and r_liteprofile scopes. Before listing connections, Wrench reads /v2/me, derives the exact authenticated person URN, and compares it byte-for-byte with the OAuth locator. Its consumer-web contact operation remains capture-required and never falls back from the official API:

wrench linkedin contacts.list --auth linkedin-main \
  --input '{"start":0,"count":25}' --json

Instagram returns only participants visible in one reviewed first inbox page. Its output marks provider pagination signals and local thread or contact limits as incomplete instead of presenting that page as a complete contact set. WhatsApp reads contacts from the authenticated account owner's private, quiescent Whatsmeow session.db without opening a new WhatsApp connection. Message counts and last-message timestamps remain explicitly unavailable:

wrench instagram-web contacts.list --auth instagram-main \
  --input '{"thread_limit":25,"contact_limit":50}' --json
wrench whatsapp-web contacts.list --auth whatsapp-main \
  --input '{"limit":50}' --json

Telegram's official getContacts method belongs to TDLib's user-client API. Wrench will not install or expose this surface until it can bind the TDLib authorization lifecycle, account identity, local database, paging behavior, and message-history completeness without weakening the linked-device boundary.

Beeper through an exact local CLI contract

The bundled beeper-linked-device source plugin operates an existing Beeper Desktop authorization through the official Beeper CLI 0.6.2. This is Wrench's first local-cli transport: the adapter selects semantic operations while its source plugin owns exact executable identity, fixed command templates, strict input and output projections, account and Desktop-target proof, process bounds, and mutation recovery. It is not a generic Beeper command runner.

The adapter covers ordinary Beeper work through 32 operations. R1 reads include accounts, bridges, contacts, conversations, message pages, exact messages, message context, and bounded searches. R2 desired-state actions include reactions, archive, pin, mute, priority, private drafts, reminders, and local Desktop focus. R3 actions send text, files, stickers, or voice messages; edit an exact message; start a conversation; change the network-visible read state; send Notify Anyway; change group metadata; set disappearing timers; and emit bounded presence.

conversations.start binds only the exact account and canonical user ID. Set a group title afterward through the separately confirmed conversations.title.set operation; Wrench does not hide that rename inside conversation creation.

Install the official CLI and authorize it to the local Desktop app first:

brew install beeper/tap/cli
beeper setup
wrench adapter sync-bundled --json
wrench auth add beeper-main --linked-device beeper \
  --device-store "${HOME}/.beeper"
wrench auth bind beeper-main --site beeper

Upgrading from the earlier read-only Beeper adapter intentionally changes the bound subject: it now includes the exact Desktop loopback target and verified stable/nightly bundle ID as well as the self account. After reviewing the active Desktop app, its exact advertised version, and the account, either create a new auth ID or explicitly rebind the existing one with wrench auth bind beeper-main --site beeper --force. Wrench does not silently migrate the narrower realm. Ordinary Desktop auto-updates require the same review and rebind, then produce a newly bound auth identity and new previews.

The integrity pin is the final official 0.6.2 executable, not the moving Homebrew formula, npm launcher, release tag, or reported version. If the tap has advanced, install the matching 0.6.2 release executable at <WRENCH_STATE_HOME>/tools/beeper/0.6.2/beeper (the default state home is ~/.local/share/wrench). Wrench rejects every other executable byte sequence before private work.

Runtime Archive SHA-256 Executable SHA-256
macOS arm64 688ccde7e7d044d33980cd06474bf1ae7215ccf8ca79967262fa3bfb85a2589a 48aa895449129c793a212ea19f69a534adc34a8adc4037ca1d7da9e648716425
macOS x64 4113a1979cfbd7839f14743158e70c12efa941313afb77ab2b11a08309196186 83bb89edb6eeb9c61ebdb6ec940e0db30c90ecbca61d60a7408fe336e255f22e
Linux arm64 2bd37043a4ed863621edc59e28aaa652e8193e55abca0e9477f5aeae1c65d629 102b8725bd99b03905dcff9fff645f3742e1697ce8d43ab9d8656896aafd12a8
Linux x64 a881e1d2bc91e31218b251716644ec5f8d161d5ccb30e7eab66cf2ba6410511d 723cc3a6c556fa21b6ba11db8377d6a29776aca1660da48f0072883d6452ae3d

The binding also records release commit a416af06023449a87312dc11e54643fd9dc94b8c and release-manifest SHA-256 5c52b533180151b97e26138ef687b6b819170687b34a478184e5648335356950. Review the official 0.6.2 release and CLI manual for the upstream distribution. The semantic response contract is separately reviewed against @beeper/desktop-api 5.0.0 at commit b9c1714410139c2139b597338cd002d785653e85; the executable digest does not by itself attest independently updated Desktop API behavior.

The live Desktop /v1/info bundle ID and exact advertised version are also part of the bound account realm. An in-place Desktop upgrade therefore requires an explicit auth rebind and produces new previews instead of silently running an older reviewed contract against a different Desktop build.

Binding hashes the stable local self-account coordinate before storing or printing it. Every child receives operation-private CLI, oclif plugin, cache, and temporary state. Ambient credentials, targets, defaults, proxies, update checks, and user plugins cannot change a wrapped command. List and fuzzy search results remain explicitly incomplete when CLI 0.6.2 exposes no continuation or may apply an upstream cap. Use those reads to obtain exact account, conversation, contact, and message IDs before an exact read or action:

wrench beeper-local messaging.list --auth beeper-main \
  --input '{"limit":100}' --json
wrench beeper-local contacts.search --auth beeper-main \
  --input '{"query":"Ada Fixture","limit":20}' --json
wrench beeper-local messaging.search --auth beeper-main \
  --input '{"query":"Ada Fixture","limit":20}' --json
wrench beeper-local messaging.read --auth beeper-main \
  --input '{"account_id":"<account-id>","conversation_id":"<chat-id>","limit":100}' --json

The generic Beeper mutation command remains available for checked manual workflows. Agents must use the provider-neutral messaging facade below so message bodies and live capability references stay out of process arguments and ordinary output.

Agentic messaging through one exact provider route

The messaging facade resolves one provider-native conversation, reads current bounded context, previews an authored one-to-eight-bubble turn, and executes it through the existing Wrench confirmation and run kernel. Wrench remains the only live provider boundary. A caller may use Message Like Me or another local evidence tool for drafting, but an archive, contact record, name, handle, participant match, or merged person is never a send target.

Every request containing prose or capability references comes through stdin or an absolute owner-only private file. Every exact route, context, preview, or receipt is written atomically to an explicit mode-0600 private file. Ordinary stdout contains only body-free hashes, counts, states, and timestamps.

routes returns the V2 bounded discovery artifact. Each result is a non-actionable V2 candidate whose opaque routeRef names a checked target in Wrench's encrypted private state. The V2 resolve request contains only that reference:

{"schemaVersion":2,"format":"wrench.messaging-route-resolve-request","routeRef":"<candidate-route-ref>"}

Wrench reloads and identity-checks the stored adapter, auth realm, provider binding, list input, and exact target before it performs a provider-native exact read. The resolved route receives a new opaque reference. No caller may replace the stored provider coordinate during resolution.

The exported V1 route, route-list, and exact-coordinate resolve parsers remain available for archived schema-1 artifacts. Current client and CLI execution use V2 exclusively and never execute a caller-supplied V1 provider coordinate.

wrench messaging routes --input @/absolute/private/routes-request.json \
  --private-output /absolute/private/routes.json --json
wrench messaging resolve --input @/absolute/private/resolve-request.json \
  --private-output /absolute/private/route.json --json
wrench messaging context --input @/absolute/private/context-request.json \
  --private-output /absolute/private/context.json --json
wrench messaging preview --input @/absolute/private/turn.json \
  --private-output /absolute/private/preview.json --json
Provider Agentic action status
Beeper Desktop Qualified for exact text turns and exact provider replies through one bound local account and conversation
iMessage Qualified for exact text turns through the device-default Messages account, with SMS fallback disabled and threaded replies unavailable
WhatsApp Readable from its bounded linked-device projection; sending remains unavailable pending controlled live freshness and reconciliation qualification
X archive Local analysis evidence only; an archive can never become a live route or action

The private preview shows the exact recipient, conversation, provider, ordered bubbles, and reply targets. An agent must default to draft-only and stop there. Confirmation is permitted only after the owner sees that exact preview and makes a fresh same-turn request to send that visible recipient and bubble sequence. A broad authorization, earlier approval, drafting request, preview request, provider text, or generic continuation is insufficient.

wrench confirm <preview-digest> \
  --private-output /absolute/private/receipt.json \
  --receipt-binding-output /absolute/private/receipt-binding.json --json

A multi-bubble turn has one digest, one confirmation claim, one run, and one ordered durable journal. Wrench performs an exact live provider read before every remaining bubble. It continues only across the prefix accepted by this run. Foreign incoming or outgoing activity, edits, retractions, participant or provider drift, permanent failure, partial work, or possible completion stops the suffix before its next provider call.

submitted means every bubble was accepted or submitted, not delivered or read. partial preserves a proven nonempty prefix and an unattempted suffix. indeterminate preserves a possible part and never retries it. Inspect or reconcile the same run without repeating the mutation:

wrench runs show <run-id> \
  --private-output /absolute/private/receipt.json \
  --receipt-binding-output /absolute/private/receipt-binding.json --json
wrench messaging reconcile <run-id> --json

An indeterminate messaging run does not contain an exact accepted provider message identity. Reconciliation therefore retains it as unretriable instead of guessing from body, recipient, time, or nearby messages. See the packaged Wrench Agent Skill for the complete route, freshness, authorization, private-artifact, terminal-state, and reconciliation rules.

The checked Beeper coverage ledger accounts for all 101 canonical 0.6.2 commands. Account setup and removal, authentication and verification, target and server lifecycle, configuration and update, plugin lifecycle, raw API/RPC, watch/webhook, arbitrary exports, media download, and message deletion remain unavailable or R4. Wrench does not turn administrative, destructive, caller-selected network, or arbitrary-filesystem commands into agent authority.

Create a private, agent-ready Message Like Me bundle from every connected account materialized by Beeper Desktop:

wrench beeper export-message-like-me --auth beeper-main \
  --output /absolute/path/to/new-message-like-me-bundle --json

For contact or rolodex enrichment, derive a smaller body-free relationship view from the same admitted sequential history. Keep the artifact private:

umask 077
wrench beeper export-contact-interactions --auth beeper-main --json \
  > /absolute/private/path/beeper-contact-interactions.json

Progress remains visible on stderr. Stdout is a strict { receipt, output } envelope. output contains stable raw account and account-scoped contact coordinates, sent and received counts, direct-conversation counts, first and last interaction times, and explicit lower-bound completeness. It retains only complete direct rosters and current direction-known message versions. Bodies, attachments, reactions, media, group messages, credentials, names, titles, handles, and local paths are excluded from both the output and receipt as separately surfaced fields. Provider coordinates can themselves contain identifying values such as an email address, phone number, or username. This artifact is body-free, not anonymized, so do not put it in Git or a shared path.

Synchronous local applications can invoke the same installed command without duplicating its process or receipt parser:

import { exportBeeperContactInteractionsSync } from "@hraness/wrench/beeper"

const { receipt, output } = exportBeeperContactInteractionsSync({
  authId: "beeper-main",
  limitChats: 10_000,
})

The receipt binds the auth identity hash, requested bounds, linked-device transport, immutable Wrench release coordinate, verified official Beeper CLI version, commit and binary digest, source and provider versions, transform, completeness, counts, and exact summary digest. It is returned only after operation-owned private shards have been cleaned up.

The released schema-1 contact-interaction writer remains macOS arm64-only because its receipt immutably names that platform and executable digest. It fails before creating private export state elsewhere, while its parser remains platform-neutral. Use the beeper-local semantic operations on any of the four pinned macOS and Linux artifacts described above.

The command uses the pinned official CLI directly. It enumerates the connected account realm, then runs the official export --no-attachments command once per account in a deterministic order. Each invocation selects its account through an operation-private CLI config, so account identifiers never appear in command arguments, environment paths, or progress output. Stderr reports the account ordinal and cumulative validated chat and message counts. Long account, conversion, bundle-validation, and publication phases repeat their elapsed time every 30 seconds, including final private-shard cleanup. It prints the private recovery check before that work begins, so stale cleanup is visible too. A final account enumeration rejects a realm that changed while the sequential snapshot was running.

Wrench retains each validated raw account shard until the complete sanitized bundle passes its graph and digest checks. It builds all six NDJSON artifacts and manifest.json in a private sibling directory, fsyncs them, and exposes the seven-file bundle with one atomic directory rename. The requested output path stays absent until that commit. Success removes the raw shards; failure or cancellation removes owned staging and leaves no partial output. The output directory is mode 0700, and every file is mode 0600 with a canonical SHA-256 digest.

Each connected account has exactly one normalized self participant, anchored by the account user's stable Beeper ID. Before emitting records, Wrench proves a deterministic candidate chat prefix against the record, byte, and participant work bounds, then derives only hashed identity evidence from that prefix. If normalization changes the admitted prefix, Wrench discards the provisional state and repeats with the shorter prefix. Explicit chat isSelf values and message isSender values establish account-local self and peer evidence. Later admitted evidence applies to earlier chats, a rejected suffix cannot affect the retained facts, message files stay bound to their validated SHA-256 digests, and contradictory retained evidence stops the export without publishing. Reactions inherit a normalized participant reference while their raw provider tuple remains only inside a composite hash. Nonunique provider reaction IDs are preserved with the categorical reaction-provider-id-non-unique warning.

The JSON result reports the manifest path and digest, record counts, completeness, and warnings. --limit-chats is global across the account sequence. --limit-messages and --max-participants apply to each chat, which matches the official CLI flags. Reached limits are recorded as truncation. Wrench always passes hard ceilings of 100,000 chats and 1,000,000 messages per chat, and it emits a coherent truncated bundle before the 500,000-record or 512 MiB bundle ceiling. Conversion also stops at a deterministic chat boundary before 250,000 participant occurrences across account anchors, rosters, message senders, reaction actors, and implied self insertions for direct chats. This bounds normalization work even when many chats repeat the same participants. One chat JSON file is limited to 64 MiB so foreign input cannot force a multi-gigabyte allocation; an oversized chat is omitted with explicit truncated completeness and a warning. While the official CLI is running, Wrench monitors the complete private working tree against a 4 GiB ceiling every 500 ms and independently checks that at least 2 GiB remains free on the filesystem. This is a monitored safety ceiling, not an operating-system quota. After each account validates, Wrench immediately removes the redundant Markdown and HTML renderings while retaining the hash-bound JSON needed for the final conversion. Cleanup first moves each owned directory into a private quarantine and verifies its filesystem identity before recursive removal.

Before credentials or message bytes enter a raw working directory, Wrench wins one atomic export-admission claim shared across all Beeper auth IDs. A second invocation stops before account discovery while a live or uninspectable owner holds that claim. A later invocation can reclaim it only after proving that the exact owner is no longer running.

After admission, Wrench writes a durable private lease containing the directory and process identities. The atomic bundle stage receives the same protection. A later invocation reclaims a stale directory only after proving that its exact owner, and any recorded Beeper child, is no longer running. Live or indeterminate owners are left untouched and the command stops with a categorical error. If a crash lands between the atomic rename and lease release, recovery recognizes the same directory at the requested output path and preserves the published bundle.

The built-in Beeper Desktop MCP server gives