@kentcdodds/bluesky
Bluesky and AT Protocol helpers using package-storage handle plus blueskyAppPassword.
AGENTS.md
135 lines · 4.9 KB · Markdown@kentcdodds/bluesky — agent notes
Human setup and intent live in README.md. This file is for
agents: imports, smoke/dryRun snippets, and edge cases. Secrets by name
only — never paste values. Do not disable live webhooks or jobs.
Secrets / storage
| Kind | Name | Notes |
|---|---|---|
| User secret | blueskyAppPassword | AT Protocol session create |
| Package storage | blueskyHandle | Default actor/handle |
Defaults: auth service https://bsky.social; public AppView
https://public.api.bsky.app. Pass service to override, or
authenticated: false to skip the session on reads that allow it.
Import paths
| Export | Import |
|---|---|
| overview | kody:@kentcdodds/bluesky |
| smoke-test | kody:@kentcdodds/bluesky/smoke-test |
| request | kody:@kentcdodds/bluesky/request |
| resolve-handle | kody:@kentcdodds/bluesky/resolve-handle |
| get-profile | kody:@kentcdodds/bluesky/get-profile |
| search-posts | kody:@kentcdodds/bluesky/search-posts |
| get-author-feed | kody:@kentcdodds/bluesky/get-author-feed |
| get-post-thread | kody:@kentcdodds/bluesky/get-post-thread |
| list-notifications | kody:@kentcdodds/bluesky/list-notifications |
| create-post | kody:@kentcdodds/bluesky/create-post |
| reply-post | kody:@kentcdodds/bluesky/reply-post |
| delete-post | kody:@kentcdodds/bluesky/delete-post |
| like-post | kody:@kentcdodds/bluesky/like-post |
| repost | kody:@kentcdodds/bluesky/repost |
| follow | kody:@kentcdodds/bluesky/follow |
| unfollow | kody:@kentcdodds/bluesky/unfollow |
Prefer static kody:@kentcdodds/bluesky/... imports from execute. Do not lead
with packages.invoke.
Smoke test (read-only)
import smokeTest from 'kody:@kentcdodds/bluesky/smoke-test'
export default async function main() {
return await smokeTest()
// => { ok: true, did, handle, profile }
}Read helper:
import getProfile from 'kody:@kentcdodds/bluesky/get-profile'
export default async function main() {
return await getProfile({ actor: 'kentcdodds.com' })
}dryRun / confirm (writes)
Write helpers dry-run unless confirm: true after explicit user approval:
import createPost from 'kody:@kentcdodds/bluesky/create-post'
export default async function main() {
return await createPost({
text: 'Watch this: https://example.com',
externalCard: {
uri: 'https://example.com',
title: 'Example video',
description: 'A useful video.',
thumbnailUrl: 'https://example.com/thumbnail.jpg',
},
dryRun: true,
})
// => { dryRun: true, action: 'create-post', payload: { ... } }
}Same guard pattern for delete-post, like-post, repost, follow,
unfollow. Mutating request bodies also dry-run without confirm: true.
Inbound webhooks
Declared webhooks (do not disable):
| Webhook name | Export |
|---|---|
create-post | ./create-post |
delete-post | ./delete-post |
get-post-thread | ./get-post-thread |
Inbound POSTs map to those exports with inputMode: params. Mutating webhooks
still honor confirm / dryRun — never treat a webhook hit as auto-approval to
publish. Minted webhook URLs are credentials; never paste them in chat.
Edge cases
create-postauto-adds clickable facets for HTTP(S) URLs unless callers passfacetsorautoLinkFacets: false. Facet indexes are UTF-8 byte offsets.externalCard.thumbnailUrl: helper fetches, fits under Bluesky’s 1 MB blob limit (Cloudinary JPEG derivative when needed), then uploads. Dry-runs reportoriginalBytes,uploadBytes,mimeType,fittedinstead of uploading.search-postsdefaults to authenticated search: session onbsky.social, thenapi.bsky.appwith the JWT (public AppView search is blocked from Kody workers; do not createSession againstapi.bsky.app— the app password is not approved for that host).- Smoke/auth must never return session JWTs or app-password values.
- Always prefer dry-run before any live write; smoke is not permission to post.
Replies (do not silently post standalone)
Bluesky expects reply: { root: { uri, cid }, parent: { uri, cid } }.
create-post maps aliases (replyTo, parent+root, parentUri/parentCid/
rootUri/rootCid, replyToUri/replyToCid) and rejects unknown keys.
Dry-run / confirm results include mode: 'reply' | 'standalone'. Prefer
./reply-post when the user asked to reply — it refuses without parent refs.
Input schema (delete/like/follow/repost)
./delete-post, ./like-post, ./repost, ./follow, and ./unfollow parse with
Remix Schema (unknownKeys: 'error'). Aliases:
- delete:
uri←postUri/atUri/post(orrepo+rkey) - like/repost:
uri+cid←postUri/atUri/post+postCid, orsubject: {uri,cid} - follow/unfollow:
actor←handle/did/user/username(unfollow alsouri/followUri)
Unknown keys get did-you-mean hints. Prefer dryRun: true before live writes.