Which archive do I want?
| You run | Archive |
|---|---|
| macOS, Apple Silicon | aarch64-apple-darwin |
| macOS, Intel | x86_64-apple-darwin |
| Linux x86-64 | x86_64-unknown-linux-gnu |
| Linux x86-64, static (Alpine, containers) | x86_64-unknown-linux-musl |
| Linux ARM64 (Graviton, Pi 64-bit) | aarch64-unknown-linux-gnu |
| Windows x64 | x86_64-pc-windows-msvc |
Each archive ships with a .sha256 next to it. The macOS binaries are
not signed: Gatekeeper blocks them on first launch, which you clear with
xattr -d com.apple.quarantine rustedin or right-click → Open.
Upgrading from 1.x
Every 1.x command moves under rustedin linkedin … — the
README carries the full
mapping table. Your rustedin.json is migrated automatically the first time
2.0 reads it; nothing to do by hand.
rustedin becomes multi-platform. It absorbs rustameta — a sibling CLI for
Facebook Pages and Instagram — into a single binary, a single config file and a
single OAuth story, and grows a broadcast command that publishes the same
content to all of them at once.
Added
- Facebook Pages:
facebook post(text and/or link),facebook photo
(single photo, or multi-photo viaattached_media),facebook video, all
with--schedule(validated against Meta's 10-minute / 75-day window) and
--draft. - Instagram:
instagram post(image, video, or a 2–10 item carousel),
instagram reel,instagram story, driven by the container → poll → publish
handshake. Local images are relayed through the linked Facebook Page as
unpublished photos so the container can ingest their CDN URL; local videos use
the resumable upload protocol.instagram publishrecovers a container whose
processing outlasted the poll timeout, andinstagram limitreports the
24-hour publishing quota. - Meta account management:
meta setup,meta auth(one Facebook Login
covers both platforms),meta tokento adopt a Business system user token,
meta pages --refresh,meta useto pin a default Page,meta getas a raw
Graph API escape hatch.appsecret_proofis sent on every call. broadcast— the same--text/--image/--linkfanned out to
severalplatform:account[/page]targets in parallel, mapped onto what each
platform actually accepts. Failures are per target,--dry-runprints the
plan without publishing, and the process exits non-zero if any target failed.accountsandstatusat the root, aggregating every platform;
--checkvalidates Meta tokens against/debug_token.migrate --from <file>— folds another config file into this one. Written
for the 1.x → 2.0 move: point it at arustameta.jsonand its app credentials
and accounts land under themetakey. Nothing already configured is
overwritten unless--forcesays so.- Cargo features
linkedinandmeta(both on by default) to build a
smaller, single-platform binary. CI checks each one on its own. - Retries with exponential backoff on LinkedIn calls, which previously had none.
- Actionable LinkedIn errors: the
serviceErrorCodeenvelope is reduced to one
line, with a hint naming the fix for 401, 403, 422, 426 and 429. linkedin auth --portto move the OAuth callback off a busy port 8765.
Changed
-
Breaking — the CLI is grouped by platform. Every 1.x command moves under
linkedin(aliases:li,fb,ig, for the other groups):1.x 2.0 rustedin setup --app=…rustedin linkedin setup --app=…rustedin auth --account=…rustedin linkedin auth --account=…rustedin accountsrustedin linkedin accounts(or rootaccountsfor every platform)rustedin statusrustedin linkedin status(or rootstatus)rustedin postrustedin linkedin postrustedin resharerustedin linkedin resharerustedin sharerustedin linkedin sharerustedin get-postrustedin linkedin get-postrustedin commentsrustedin linkedin commentsrustedin profilerustedin linkedin profile -
Breaking — the config file is namespaced per platform, so the same alias
can name a LinkedIn company page and a Meta account without colliding:
linkedInAppand the top-levelaccountsmap move underlinkedin.appand
linkedin.accounts. 1.x files are upgraded automatically on load; nothing
to do by hand. Unknown top-level keys now survive a rewrite. -
A malformed config file is an error instead of a silent reset — overwriting a
corrupt file would have destroyed the only copy of the stored tokens. -
LinkedIn's commentary and title limits are counted in characters, not
bytes. A 3000-character post full of accents or emoji was previously rejected. -
LinkedIn calls now go through the shared pooled HTTP client, with connect and
request timeouts, instead of building a client per request. -
Percent-encoding of the LinkedIn authorization URL is complete, rather than a
hand-rolled substitution of six characters. -
statusreports LinkedIn expiry dates alongside the remaining days.
Fixed
- Publishing calls are never repeated after a 5xx or a timeout. Only
failures that provably did not reach the platform — a refused connection, or
an explicit "throttled, not processed" — are retried on a write. A retry on a
timed-out publish could have posted twice. - A concurrent
reshare(and nowbroadcast) refreshes its tokens up front,
sequentially. Two tasks renewing at the same time each rewrote the whole
config file from its own stale copy, losing the other's new token. - Downloading a remote
--imageno longer decodes the bytes as UTF-8, which
corrupted every non-ASCII byte of the image.
Dependencies
- Bumped:
rand0.8 → 0.9 (migrated to the newrng()/random()API),
tokio1.52.3,clap4.6.1,serde_json1.0.150,open5.3.6. - Added, behind the
metafeature:hmac0.12,sha20.10 (for
appsecret_proof), andreqwest/multipart. - Pinned toolchain via
rust-toolchain.tomlso local and CI use the same Rust /
clippy version. scripts/check.shruns the exact CI checks locally in one command; the opt-in
.githooks/pre-pushhook blocks a push when they fail
(git config core.hooksPath .githooks).- CI: bumped
actions/checkoutto v7 and installs the pinned toolchain via
rustup show.