This repository is the canonical source for the Radius Red public web site, tech docs and blog.
When adding or updating blog articles, do it in this repository.
Every post requires valid front matter at the top of the file:
---
layout: default
author: Your Name
title: Post title goes here
date: YYYY-MM-DD
description: One-sentence summary, used in feed and listings
tags: [tag1, tag2, tag3]
---layout: Required. Set todefaultfor all posts.author: Required. Author name displayed in the post byline.title: Required. Displayed as the post heading and in listings.date: Required. Sets publication order and controls visibility (see Build Visibility Controls below).description: Required. Used in feed summaries and on the blog homepage.tags: Optional. Comma-separated list of topic tags.
Posts should contain article content without:
- No post title. The template renders the title from front matter
titlefield. - No byline or date. The template renders publication metadata from front matter.
- No license footer. The template appends the Apache 2.0 license footer automatically.
Start your content with the first paragraph or section heading (## level 2 or deeper).
Example structure:
---
layout: default
author: Wordy
title: Why we chose Postgres for the event store
date: 2026-04-30
description: Technical decision on data store selection for our event pipeline.
tags: [engineering, data, architecture]
---
## The Challenge
Our event pipeline requires high-fidelity, ordered writes...
## Why Postgres
We evaluated three options...- Create posts in
docs/blog/posts/and must include a valid front matterdate. - The site build will handle future dated posts and ensure they do not appear until the publish date.
- Public-facing content in this repository must not reference internal systems, internal issue trackers, private repository paths, or non-public workflow tools. In practice, do not link to
RAD-*issues, private repos, or internal orchestration platforms from site copy.
uv sync && uv run zensical serveshould create a local site on localhost:8000
Announcements go out from Radius Red's own accounts — radiusred.bsky.social
and the LinkedIn Page linkedin.com/company/radiusred — through the social
package in this repo. It is stdlib-only; run it with uv run -m social.
Credentials never live in this tree. They are read from the environment
first, then from ~/.config/codecrew/social.env (mode 0600; --env-file to
point elsewhere). Keys: BSKY_HANDLE, BSKY_APP_PASSWORD,
LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET, LINKEDIN_ACCESS_TOKEN,
LINKEDIN_REFRESH_TOKEN, LINKEDIN_ORG_URN; optional LINKEDIN_VERSION
(API version, YYYYMM), LINKEDIN_REDIRECT_URI, BSKY_PDS. The two
*_EXPIRES_AT keys are maintained by the tool.
uv run -m social check # prove auth without posting
uv run -m social post --to bluesky --to linkedin \
--text-file announce.txt --link URL --title "…" --dry-run # show the requests
uv run -m social post --to bluesky --to linkedin \
--text-file announce.txt --link URL --title "…" # send them
uv run -m social auth linkedin # re-consent (browser leg, human)
uv run -m social comment --urn urn:li:share:123 \
--text-file links.txt # the first comment on a LinkedIn sharechecklogs in to Bluesky, introspects the LinkedIn token (refreshing it when it has under a week left, and writing the new tokens back to the env file when that is where they came from), and lists the Pages the token administers — writingLINKEDIN_ORG_URNto the env file when exactly one Page is administered and the key is not yet set.postpublishes the same text everywhere by default; Bluesky allows 300 graphemes, so give it its own copy with--bluesky-text-filewhen the LinkedIn version runs longer. URLs in the text become links; on Bluesky[label](url)becomes display text carrying the link (only the label counts toward the 300) and#hashtagsbecome tag facets — without the facet a tag is plain text and reaches nobody, so tag deliberately; on LinkedIn#hashtagsbecome hashtag entities and URLs are left unescaped.--linkadds a link card (Bluesky) / article (LinkedIn). Posted texts are kept inannouncements/. Always--dry-runfirst — it prints the exact request bodies and touches no network. Output is one JSON line per network with the post URL.commentposts a comment on a LinkedIn share as the Page, given the URNpostprinted. It is where a LinkedIn post's links go: inline URLs cost the post reach, so the body says "links in the first comment" and this command supplies it, immediately after. Comment text is plain — URLs are not escaped — and--dry-runprints the request likepostdoes. The standing rules for announcements, including this one, are inannouncements/README.md.auth linkedinis the re-consent playbook: it prints the consent URL, catches the redirect onlocalhost:8765(or--pastethe code), exchanges it, and stores the tokens. Someone signed in to LinkedIn as a Page admin has to click through; no agent can.
Rotation calendar: LinkedIn access tokens last 60 days and refresh
themselves; the refresh token lasts a year from the last consent, after which
auth linkedin is needed again (check prints the date). Bluesky app
passwords do not expire; revoke and re-mint from the account's settings. The
LinkedIn API version pinned in social/linkedin.py retires after about a
year — a 426 NONEXISTENT_VERSION means bump it.