Skip to main content

author-feed

❖ Communityv0.1.0★ 0

Read one public profile and watch one account, with profile_get, author_feed and author_watch.

Open in Hermes Desktop
hermes plugins install author-feed

What it adds

Tools 3

profile_getauthor_feedauthor_watch

README

From the reviewed commit 4a302be ↗; it updates when the author re-pins.

author-feed

Read one public profile and that author's recent posts, and watch one saved account, built on the public AppView at public.api.bsky.app.

The plugin sends HTTPS GET only to https://public.api.bsky.app, with no key and no Authorization header. The only paths are /xrpc/app.bsky.actor.getProfile and /xrpc/app.bsky.feed.getAuthorFeed. It does not call search, a firehose, a PDS, api.bsky.app, bsky.social, or an image host. There is no POST, PUT, or DELETE, and it starts no program on your machine. Redirects (301, 302, 303, 307, 308) are not followed. Each GET stops if 20 seconds pass with no data, and a single GET is also cut off after 30 seconds in total, counted from before the connection opens, so a slow header or a slow body cannot hold that GET open longer. One watch run reads at most 3 pages, so at most three such GETs, and therefore at most 90 seconds. It is not retried in the same run. On profile_get and author_feed the timeout text is: Not fetched. Saved state is unchanged. 取れなかった。保存は変わっていない。 Those calls do not write the state file. On author_watch the same failure leaves seen URIs unchanged, records the failure, and says so. It does not say the request may have taken effect.

profile_get requires actor and returns did, handle, displayName, followersCount, followsCount, and postsCount. It drops avatar, banner, description, and pinnedPost. author_feed requires actor and requests one page with limit=10 and filter=posts_no_replies. It does not send includePins, a caller-chosen filter, or a caller-chosen limit, and it does not sort the page again. author_watch takes no arguments. Unknown arguments are rejected before any request, as is an actor that is empty, holds whitespace, or is longer than 253 characters. The two read tools do not write seen post URIs. A manual read does not advance them.

Replies are not read: the feed is requested with filter=posts_no_replies, so a reply, including the account's own thread continuation, is not listed by author_feed and is not announced by the watch.

A repost is kind=repost and the text is the original author's, with author_handle. It is not announced as the watched account's post. A pin is not treated as new and its URI is not stored. On 2026-10-08, posts_no_replies for bsky.app still included reposts by aster.id and kenjennings.bsky.social. includePins=true with limit=3 returned an older pinned post; this plugin does not send that parameter. An empty feed array is an empty page, not a failure, and does not clear seen URIs. postsCount is not the cursor. A missing actor was HTTP 400 InvalidRequest / Profile not found, not an empty success. HTTP 200 with an empty body, non-JSON, an error field, or no feed array is a failure. BlockedActor and BlockedByActor were not seen live; if that error name is returned, the call fails.

Empty post text with an embed returns embed_type as the embed $type only. Text longer than 300 characters is cut and text_truncated is true. At most 20 label val strings are returned, each cut at 100 characters. Images and blobs are not downloaded. The request asks for 10 items. A page can be shorter than 10. On 2026-10-09, getAuthorFeed for actor=bsky.app with limit=10 and filter=posts_no_replies returned HTTP 200, a page shorter than 10, and a cursor. The plugin does not say the whole history was seen. author_feed is one GET and does not follow that cursor.

hermes author-feed schedule saves one account after getProfile succeeds. A failed schedule leaves the old DID, seen URIs, and job in place. The same DID keeps seen URIs and updates the handle string. A different DID clears seen URIs and sets the baseline flag false; the reply names both accounts, how many saved URIs were cleared, and that the next run stores a new baseline without announcing it. There is one cron job named author-feed, stored by id. Updates go to that id. The plugin does not remove a job by name. hermes author-feed unschedule removes the job and leaves the state file, including seen URIs. Run unschedule before hermes plugins remove. After the plugin is removed, that CLI is gone, so remove the leftover job with hermes cron remove <id>, and add -p <name> when the profile is not default. If the saved id is already gone, unschedule clears it and says the job is already gone. hermes author-feed status reads the file, checks whether the saved id still exists, and does not fetch. If that id is gone, or no id is stored, status says so and prints no hermes cron run or hermes cron remove line, because both need an id.

The default interval is every hour. Omitting --every on a later schedule keeps the saved interval. The first schedule with no saved interval is every hour. Intervals faster than every 15 minutes, intervals that are not a multiple of 15 minutes, intervals longer than every 7 days, and a cron expression you type are rejected. The words you see are every hour, every 15 minutes (or every N minutes for another allowed multiple, such as every 45 minutes), or every N hours for a whole number of hours, so a mistyped interval is not printed as six million minutes. author_watch reads at most 3 pages when none of the saved posts or stored repost marks is on the current page. It does not retry a failed GET in the same run. A PDS request limit is not cited as an AppView limit. No fee is stated for this endpoint. There is no daily cap.

Delivery uses an allowlist: origin, local, all, bot-chat, bot-chat:<profile>, the names on the Hermes list of delivery platforms, and names added by loaded plugins. That Hermes list is a fixed set of platform names; it is not a list of what you have set up. Nothing here checks that the platform is configured, and neither does hermes cron doctor, so an accepted name whose platform is not set up silently drops the report. For any target other than origin, local, and all, schedule prints that caution with the name. The default deliver target is local, and local does not send to chat. If the Hermes list cannot be read, only origin, local, and all are accepted. cli, cron, api_server, and words that look like a schedule are rejected, and the existing job stays. Replacing a target echoes the previous one. bot-chat spends another model turn on the fetched text. It is not model-free.

The cron prompt tells the model to call author_watch with an empty argument object only. If notify is true, reply with message only. If notify is false, reply [SILENT]. If the tool is missing, say the watch is not running. The prompt does not ask the model to search or to complete post text. A model may add words, and that cannot be stopped. The job is a model turn. A tool-calling run can take two or more requests to your model. interpreter is not passed, and the job is not no_agent.

Seen URIs advance only when author_watch is confirmed as the cron job and the fetch succeeds. When the plugin cannot tell that this run is the cron job, including a separate plugin host process, it says it cannot monitor, names plugins.isolation: in_process as the setting to check, sends no GET, and leaves saved URIs unchanged. It does not say there is nothing new. It posts that notice on every run. A manual call does not quiet the next cron. An environment variable named HERMES_CRON_SESSION alone does not count as that proof. The first successful cron run stores up to 3 pages and does not announce them. A page with no post by the account still stores the baseline. If those 3 pages were read and the feed still offered another cursor, the baseline message says so. A post found on page 2 or later may be older than that first stored page. On the baseline, pages with no post by this account say so. Repost URIs are not stored as this account's posts. Each repost row that was read is stored as a continuation mark: the reason's indexedAt and the original post's URI. One run reads at most the latest 3 pages and announces a new post by this account on them. When every row on those pages is new, with no saved post or mark, and the feed still offers another cursor, it also posts one possible-miss notice, even when no post by the account was on those pages. The rows it read are stored. When those rows are still on the pages the next run reads, that run overlaps and does not repeat the notice. When newer rows push them past the pages it reads, the same notice is quiet for 24 hours, and a new post by this account on the pages it read is still announced. A run that finds a saved post or a stored mark does not post that notice. That includes a run where nothing changed, and a run where new rows only pushed a stored mark deeper while the mark was still on the pages it read. A new post by the account that sits above a stored mark is announced. A new repost of an already seen post is a different row, so it does not stop the walk. A page shorter than 10 does not stop it either, because getAuthorFeed drops filtered rows after the limit is applied; only a missing cursor ends it. If instead the feed ran out of cursors, the whole feed was read, so nothing slipped past and that note is not sent: an account whose posts were deleted, or an empty feed, stays quiet rather than warning forever. That note is sent on the first such run, again when the gap closes and later reopens, and otherwise at most once every 24 hours. The runs in between do not repeat that note, and they still announce a new post by this account that they read. Seen URIs are not cleared. A full page that still holds a saved post does not add that note, and the plugin does not append "Older posts were not fetched." Saved URIs and continuation marks are each capped at 2000; past the cap the entries stored longest ago are dropped first, and the lookup is a set.

The same upstream failure notifies on the first time, when the cause changes, and again after 24 hours. The cause is counted per HTTP status, so a change from 503 to 400 notifies. The same status within 24 hours stays quiet. Recovery notifies once. Argument errors and a missing saved account are not counted as upstream failures. notify is present on success and failure. A successful run with nothing new has notify false and the message is "No new posts." A quiet repeat of a possible miss says the same possible miss was already reported only when that run has no new post by this account to announce. When it did read one, it announces that post and does not add that sentence. A corrupt state file is not overwritten and that run does not change seen URIs.

State stays under <HERMES_HOME>/plugin-data/author-feed, per profile. If that folder cannot be read, the plugin refuses. It does not fall back to another home path. Writes go through the Hermes write check and replace a temporary file. The file stores the DID, handle, deliver target, interval words, job id, baseline flag, your account's post URIs, continuation marks for repost rows that were read (the reason's indexedAt and the original post URI), the last failure code and its times, when the possible-miss note was last sent, and the last success time. Saved URIs and continuation marks are each capped at 2000. It does not store post text, chat speaker names, speaker ids, a key, postsCount, or the API cursor. The state file is not sent as a MEDIA: path. That folder survives hermes plugins remove: the watched DID, handle, deliver target, and up to 2000 post URIs stay on disk. To delete them, remove the folder yourself with rm -r <HERMES_HOME>/plugin-data/author-feed, using the HERMES_HOME of the profile you installed under.

The watch reads unpublished Hermes parts, and a Hermes update can stop the watch. The cron proof reads gateway.session_context for a contextvars.ContextVar whose name is HERMES_CRON_SESSION. Hermes stores that variable as _CRON_SESSION, with sentinel _UNSET. Seen URIs advance only when its value is exactly 1 and HERMES_PLUGIN_HOST_PROCESS is unset, empty, or 0. get_session_env is not called. The delivery allowlist reads cron.scheduler_delivery._KNOWN_DELIVERY_PLATFORMS and gateway.platform_registry. The profile name reads hermes_cli.profiles.get_active_profile_name and _get_profiles_root, and hermes_constants.get_hermes_home.

The read tools have no user list: anyone you allow on your gateway can call them, and hermes tools turns them off for each surface. There is no slash command. User-visible text is English first. requires_hermes is >=0.21.4. manifest_version is 2. python_dependencies is empty. The license is MIT. This repository does not include a NOTICE file. No hooks are registered, and Hermes hooks are not called again. Schedule does not ask for an approval prompt. Changing the watched account is the local CLI only.

Printed hermes cron lines come after the heading "Hermes commands for this job, for you to run:", so they read as commands to run rather than as commands already run. hermes cron run and hermes cron remove include the job id and are left out when there is no id. create, list, and status do not take an id and are always printed. When the current profile is not default, every printed line includes -p <name>. This CLI does not define -p or --profile of its own.

On 2026-10-08, origin/main 38880bd2f1e90dbc9a1aeec03af62539ee64719a had no catalog tool that calls this AppView. The one grep hit was an achievements README image size. RSS tools are a different reader. Open pull-request titles were not all read, and gh was not used.

What was tried

The design table below is 13 GETs on 2026-10-08 to https://public.api.bsky.app, 1.1 seconds apart, with no key, no redirect, and User-Agent takeshitgal-design-probe. Those calls were not a plugin install. No RateLimit header, Retry-After, or Location was returned.

Call Result
getProfile actor=bsky.app HTTP 200 with did, handle, displayName, followersCount, followsCount, postsCount
getAuthorFeed actor=bsky.app&limit=5 and no filter HTTP 200, five items, including two reposts
getAuthorFeed filter=posts_no_replies&limit=5 HTTP 200, and reposts were still in the page
getAuthorFeed filter=posts_and_author_threads&limit=5 HTTP 200, same five items
getAuthorFeed includePins=true&limit=3 HTTP 200, four items, first was a pin from 2024-10-17
getProfile missing actor HTTP 400 Profile not found
getAuthorFeed same missing actor HTTP 400 Profile not found
getAuthorFeed actor=not a handle HTTP 400
getAuthorFeed limit=0 and limit=101 HTTP 400
getAuthorFeed filter=nope HTTP 200, ordinary feed
searchPosts q=hello HTTP 403 HTML. This plugin does not call it
getAuthorFeed by the returned DID HTTP 200

Not verified

  • The 13 GETs in the table above were not a plugin install and did not run cron or a gateway.
  • BlockedActor and BlockedByActor were not seen live. If that error name is returned, the call fails.
  • No RateLimit header and no 429 were seen. A numeric AppView cap was not taken from the documentation.
  • Open pull-request titles were not all read.
  • Authenticated endpoints, writes, a firehose, and image hosts were not called.
  • posts_with_media and posts_with_video were not sent.
  • The terms were not settled here as a list of what a repeated fetch is allowed or forbidden to do.
  • The feed in that table is the 2026-10-08 page of the public account bsky.app. The 2026-10-09 check was one limit=10 page, not a full history.

Terms and marks used for the name: Bluesky Terms of Service, Bluesky Trademark Policy, AT Protocol Trademark Policy, and the public AppView note on rate limits. The product name does not include those marks. The host name and lexicon method names above are the published identifiers.

doc-markdown❖ Community★ 0

Convert one local file to Markdown with doc_convert on Linux and macOS, built on Microsoft's MarkItDown. Microsoft and MarkItDown are trademarks of the Microsoft group of companies. Disclosure — the doc_markdown toolset is on by default on every surface, including a gateway, so anyone you allow there can call doc_convert; stop it with hermes tools. It does not fetch pages. Unattended sessions still convert when Hermes calls the tool. This plugin creates no cron job. A child process runs MarkItDown with no Hermes keys in its environment; on macOS the plugin runs ps about every 10 ms to watch that child's memory. It stops 8 seconds after its converters load, at 60 seconds if loading never finishes, or above 922746880 bytes. It is not a sandbox: after realpath it may read any allowlisted path it can open. Unpublished Hermes code locates the Hermes home, and a failed check refuses the path. Removal deletes the plugin directory, not the markitdown package. Windows was not tested.

Tools
event-read❖ Community★ 0

Read one CalDAV calendar collection you run, with list_events and read_event. Disclosure — Sends CALDAV_USER and CALDAV_PASSWORD as Basic auth to your CALDAV_URL, does not follow redirects, and starts no local program. Over plain http that password travels unencrypted. The tools are on for every surface, including gateways, and the plugin has no user list of its own: anyone you allow on your Hermes gateway reads the calendar with your password. Turn a surface off with hermes tools. A watch is a cron job you create yourself with hermes cron create. Each run is at least one model turn, there is no daily cap, and the job stays until you run hermes cron remove. Under plugins.isolation: host the watch cannot run and posts a notice saying so on every run. When the watch can run, it reports a problem on the first run, when the cause changes and again after 23 hours, or on every run when its data directory cannot be written. Each request gives up after 20 seconds without data, and a whole call has no time limit. The watch relies on private parts of Hermes, so an update can stop it. State lives in plugin-data/event-read. Not tested against a vendor-hosted calendar.

Tools
index-search❖ Community★ 0

Search one index you run and watch one saved query, with index_search, index_health and index_watch. Disclosure — Sends MEILI_API_KEY as a Bearer token to your MEILI_HOST. Over plain http that key travels unencrypted, and a master or admin key is sent as given, so use a search-only key. The tools are on for every surface, including gateways: anyone you allow on your Hermes gateway can search with your key and approve a watch. Turn a surface off with hermes tools. index_watch schedule creates one cron job that runs a model turn every 15 minutes (two or more model requests when it calls the tool) with no daily cap, and it watches only the first 20 hits. The job stays after uninstall until you run hermes cron remove, with -p and the profile name when the profile is not default. Scheduling and unscheduling ask for Hermes approval every time and are refused under cron, yolo, approvals off, unattended sessions and plugins.isolation: host, where the watch only posts a notice that it cannot run, on every run. Answering "always" adds an unused line to config.yaml. The watch relies on private parts of Hermes, so an update can stop it.

Tools
ja-writing-guard❖ Community★ 0

Rule-based proofreading for Japanese business text: finds and fixes AI-style phrasing (stock phrases, monotonous sentence endings, overused symbols and Markdown, English-calque grammar) with line, column and a fix hint. It checks wording, not authorship, and rarely flags plain model output. A transform_llm_output hook checks every final Japanese answer and only reports by default; opt-in enforce mode rewrites flagged answers with the user's own model and delivers the rewrite only if numbers, names, URLs, code and negation survive and the score drops under the threshold. Disclosure: no network calls of its own; enforce mode sends each flagged answer to the user's configured model (one extra call, a second only when the first rewrite fails the checks or stays over the threshold; extra cost and up to 25 s delay); a read-only post_tool_call hook notes turns with MEDIA: lines so attachments from other plugins are kept; reports stay in process memory on local surfaces only. Disclosure — no network calls of its own; mode report (default) only logs scores; opt-in mode enforce sends each flagged final answer (with finding excerpts) to the user's configured model via ctx.llm (1–3 calls, up to 25 s delay) and replaces the delivered and stored answer when its preservation checks pass, which do not prove meaning is unchanged; reads plugins.hook_callback_timeout via a private read-only Hermes helper; eval/ dev scripts ship in the tree but are never loaded.

Tools
jp-address❖ Community★ 0

Splits one Japanese address into prefecture, city, town and the remainder with address_split, built on Geolonia's address data (CC BY 4.0, from the Digital Agency address base registry). Disclosure — Each call sends at most two HTTPS GET requests, with no API key, to japanese-addresses-v2.geoloniamaps.com or the JP_ADDRESS_BASE_URL you set (plain http only for 127.0.0.1 and localhost). The matched prefecture and municipality names go into the URL path; the house number, the lot and the building name are not sent. The tool is on for every surface, including gateways, and asks for no approval: anyone you allow on your Hermes gateway can call it. Turn a surface off with hermes tools. It writes no files and creates no cron job. A point can be coarser than the town and is not a rooftop geocode, and an address it cannot place for certain comes back as ambiguous or at the municipality instead of a guess. The publisher may change or stop the free host and suggests self-hosting for commercial use. Not tested from a Hermes model.

Tools
jp-charts❖ Community★ 0

Bar, line, stacked and 100% band charts as phone-sized PNGs for chat replies and reports, with Japanese text, 万/億 units and a required unit and source line. Every bar, point, label and tick is read back from the finished figure and compared with the parsed data before the image is returned. Disclosure: no network calls at run time; reads a local .csv/.tsv/.txt/.json only when given its path (never hidden files or the Hermes home); writes PNGs to the Hermes image cache; a transform_llm_output hook adds a forgotten MEDIA line to replies on chat platforms and cron deliveries (setting auto_attach, on by default).

Tools

← Back to the catalog · catalog built Oct 14, 2026