Commit Graph
96 Commits
Author SHA1 Message Date
Teknium d99af4187b refactor: move the Singularity terminal backend out of core into the singularity catalog plugin
The Singularity/Apptainer backend (tools/environments/singularity.py) now ships
as the official 'singularity' plugin in NousResearch/hermes-official-plugins,
registered through the terminal-environment provider seam under the same
backend name and config keys (terminal.backend: singularity,
terminal.singularity_image / TERMINAL_SINGULARITY_IMAGE, terminal.container_*).

- 'singularity' leaves BUILTIN_BACKEND_NAMES so the plugin can register it;
  every hardcoded classification set drops it (the provider declares
  is_remote/is_container/supports_streams instead).
- LEFT_CORE gains a singularity row: homes whose terminal.backend (or .env
  TERMINAL_ENV) is singularity get the catalog plugin once, on hermes update
  or agent/gateway start. An unknown-backend error for 'singularity' names
  the install command (new row field terminal_backend).
- A plugin backend receives its per-task '<backend>_image' override; batch
  runs register one for the active backend.
- BaseEnvironment's sudo-probe knob is renamed _passwordless_sudo_probe_supported:
  plugin backends set it, and the old spelling trips the install scanner.
- Core's scratch-dir disk warning and process-exit sweep were Singularity-only
  and move to the plugin; the singularity setup row, dashboard probe/row and
  config defaults/bridges go too. Docs point at the plugin.
2026-10-22 05:26:10 -07:00
Austin Pickett 2b1440bd49 fix(website): curated skill art points at the docs site, not GitHub raw on main
extract-skills.py stamped raw.githubusercontent.com/.../main/... URLs, which
break if the art moves on main. The art ships in website/static/img/catalog/,
served at hermes-agent.nousresearch.com/docs/img/catalog/; one named constant.
2026-10-19 22:57:46 -04:00
Austin Pickett 96efa73d9f Merge origin/main into austin/feat/capabilities-catalog 2026-10-16 17:53:55 -04:00
Teknium 620f3b3b45 feat(plugin-catalog): add a Nous-granted verified tier
Verified entries come from the company that makes what the plugin integrates,
confirmed by the Nous team. Only maintainers set official/verified (CODEOWNERS
review; no CI check inspects the value). Tier order everywhere: official,
verified, community. Desktop's installed-plugin pill now reads the same
catalogInstall.tier map as the install card, dropping the duplicate
skills.plugins.tierOfficial/tierCommunity keys.
2026-10-16 14:31:11 -07:00
Teknium 3b043e1900 fix: docs search monitor reads the page visitors get
The live-search probe fetched the docs page and its JS bundle with Node's
default Accept-Encoding. Vercel keeps a separate cache entry per exact
Accept-Encoding string, so after the 12:36 UTC deploy on Oct 15 the probe
read a cached page whose bundle 404'd while every browser's copy still
loaded. It then parsed the 404 error page and alerted "search is not
configured on the live site" although search worked.

Send Chrome's Accept-Encoding so the probe reads the same cache entry as
visitors. When the bundle itself fails to load, name the HTTP status and
say the page can run no JavaScript, instead of blaming the Algolia config.
2026-10-15 07:39:25 -07:00
Brooklyn Nicholson d8a4b51c35 feat(catalog): curate featured picks once for the site and the app
catalog-curation.json (was catalog-artwork.json) now holds an ordered
featured list per kind beside the artwork. Both extractors stamp a 1-based
featured rank into skills.json / plugins.json, and a shared pickFeatured
rotates through the ranks weekly, so the site and the app feature the same
entry. Without ranks it falls back to the newest official entry with art,
then an official entry with art and the most stars; nothing reads download
counts. The app reads ranks from the bundled file until the feed has them.
2026-10-14 23:45:49 -04:00
Brooklyn Nicholson bd361db200 fix(website): tolerate optional catalog artwork manifest 2026-10-14 23:43:11 -04:00
Brooklyn Nicholson adcea47191 feat(desktop): redesign capabilities catalog 2026-10-14 23:42:40 -04:00
Teknium 5e908370e2 ci: drop Telegram from the docs search monitor
The workflow no longer holds any notification secret or needs the
trusted-automation environment. A failing probe turns the run red and
records each reason as an ::error:: annotation (only the retry writes
them, so a run that recovers on the retry stays clean). Alerting is done
by whoever watches this workflow; the maintainer runs a local no-agent
Hermes cron that reads the runs with gh and messages Telegram.
2026-10-14 14:38:08 -07:00
Teknium e8b84b98d2 fix: docs search monitor review findings
- Remember the last PROBE step verdict (jobs API), not the run conclusion,
  so a setup failure cannot suppress the next outage alert or fake a
  recovery.
- Deadline every request and its body (20 s) and the whole probe (240 s),
  plus a 9 min step timeout, so a slow-drip server still ends in an alert.
- Read appId/apiKey/indexName independently from whichever algolia block
  carries all three, so field order and quoting do not matter.
- One exit path writes the summary and reason for thrown errors too.
2026-10-14 14:38:08 -07:00
Teknium 6d5af7a275 ci: hourly live check of docs search with a Telegram alert
Docs search broke in production twice in a month (a deleted Algolia
search key, then Vercel access controls) while every build stayed green,
because nothing exercised the deployed site.

website/scripts/check-live-search.mjs reads the Algolia credentials from
the bundle the live site serves, queries Algolia with the site's Origin,
opens the top results, and types into the real DocSearch modal in
headless Chromium. The Docs Search Monitor workflow runs it hourly and
after every Deploy Site run, retries once to absorb blips, and sends a
Telegram message when search goes down, every 6 hours while it stays
down, and when it recovers. Bot token and chat id live on the main-only
trusted-automation environment.
2026-10-14 14:38:08 -07:00
Maciej Lisiewski bda3aea496 ci: remove unused variables (linter) 2026-10-10 15:20:14 +02:00
Maciej Lisiewski 7ef490ed3b ci: fix PLC0206, PLC0414 linter errors 2026-10-10 15:20:14 +02:00
Maciej Lisiewski 9eab688b42 ci: ruff UP017 fixes 2026-10-09 17:34:36 +02:00
Maciej Lisiewski 32b70e9733 ci: FURB188, FURB192 linter fixes 2026-10-09 17:31:52 +02:00
ehz0ah c9a15f6b53 chore(catalog): keep OpenViking release update in the catalog entry 2026-10-09 00:55:24 -07:00
Hao Zhe 6532f07221 feat(catalog): refresh OpenViking pin and honor display titles 2026-10-09 00:55:24 -07:00
Maciej Lisiewski d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
teknium1 83097ba008 fix(desktop): keep the hermes://skill/install deep link through the catalog revert
The plugin deep link (hermes://plugin/install?catalog=) and the skill
deep link are separate features: one per kind. Reverting #119381's card
catalog should not leave skills without a link, so this restores the
skill route on its own:

- Desktop: skill-deeplink-install store, the skill route in
  deeplink-routes, its use-desktop-integrations wiring, and the
  confirm-store onConfirm/details/busy/done phases its dialog uses
  (restored from main with their tests). Its nine strings move from the
  deleted catalog.* namespace to skillDeepLink.* (en + the six locales
  that translated them).
- Website: Skills Hub "Install in Hermes" button, plus the
  source-qualified installIdentifier from extract-skills.py so the link,
  the embedded picker and the CLI command all name the exact skill
  (apps/shared/src/catalog-install.ts keeps only the skill helpers).
- Docs: skills.md "Install from the website" section, desktop.md pointer.

The card catalog UI stays reverted.
2026-10-02 10:29:28 -07:00
teknium1 6cb7a64e59 revert: Skills and Plugins card catalog browser, third landing (reverts #119381)
Reverts all seven commits of #119381 (59c54c78..476ff4dd), which
re-landed the native card-based Skills/Plugins catalog browser after it
was reverted twice (#113037 reverting #112669, #115787 reverting
#113128). Capabilities -> Skills and Plugins return to the list/detail
tabs with the embedded catalog picker they had before #119381.

Removed with it:
- Desktop catalog/ module (browser, cards, detail dialog, filters,
  reels/masonry, install switch) and skill deep-link install
  (hermes://skill/install); skills/embedded-hub-picker.tsx comes back.
- apps/shared catalog-browse.ts and catalog-install.ts.
- The `catalog` i18n namespace in every locale (+ regenerated
  locales/_keys.desktop.json); the two i18n pack tests and the keys
  test now exercise connectorsPage.searchPlaceholder instead.
- Website /skills "Open in Hermes Desktop" skill links and the
  extract-skills install identifiers; docs return to the pre-#119381 text.
- Later fixes that only patched the reverted catalog code go with it:
  5832a6eb93 + 7aba2cbbe9 (catalog download deadline),
  9e63cdc4b8 (catalog lookalike rows), c756986db2 (catalog counts),
  the catalog half of c13232e90c / f265273347.

Kept on purpose (later work that depends on code #119381 introduced):
- DialogContent `chrome` / `overlayClassName` / nested-dialog layering
  (the image lightbox uses them).
- vitest.setup.ts / src/test/jsdom.ts stubs (other suites rely on them).
- Kanban toolset wiring on the Plugins Desktop switch (cd30fdadf6,
  7ca2f5665f) and its tests, the `hermes_not_connected` server tone,
  the SearchField flex-1 fix, skill provenance helpers. SkillDetail shows
  a provenance pill only for agent/hub skills, since the pre-#119381
  labels have no `external` entry.
2026-10-02 10:29:28 -07:00
teknium1 e780748afc refactor: keep subdir/sha shape rules at the admission gate only
The runtime loader (hermes_cli/plugin_catalog.py) and the site feed
(website/scripts/extract-plugins.py) only ever see entries that passed the
structural validator, which now carries the subdir, whitespace-repo and
\Z-anchored sha rules. Every runtime subdir consumer (install, preview,
transaction) already goes through _resolve_subdir_within, which refuses an
escaping subdir. Mirroring the rules there duplicated the gate and changed
runtime behaviour for null and padded subdirs, so revert both files and
their test to main.
2026-10-01 10:46:45 -07:00
beardthelion 8444e9eb18 fix(ci): confine plugin-catalog admission gate to the pinned clone
The pinned-source job joined a PR-controlled subdir onto the clone root
with no validation, accepted file:// repos and ref names as the "pin",
and followed committed symlinks out of the clone, so the gate could
validate attacker-planted files instead of the declared commit's
contents. Harden the step end to end:

- validate repo (https://, whitespace-free), sha (40-hex, commit object),
  and subdir (plain relative path) in the parser before any use
- run the parser under python3 -I against installed ruamel.yaml so the
  PR checkout cannot shadow its imports, capture parser output, and
  reset shell variables per entry so a dead parser cannot replay the
  previous entry's fields
- confine PLUGIN_DIR and every symlink beneath it to the clone root
- include renamed and typechanged entry files in the changed set
- feed entries over fd 3 and keep plugin-executed commands off stdin so
  validate cannot drain the remaining list
- NUL-delimit both self-updater grep stages and sanitize the echoed
  filename list so crafted names cannot forge workflow commands
- mirror the same repo/subdir rules in the runtime catalog loader and
  the site extractor, and use \Z anchors so a trailing newline cannot
  split the verdict between gates
2026-10-01 10:46:45 -07:00
teknium1 80512c90dc fix(website): batch the plugin stars probe at 100 repos per GraphQL request
A single request for all 317 catalog repos now exceeds GitHub's per-query
resource limit: the reply carries partial data plus an error, the script
prints 'Probed 317', and every repo past the limit keeps its stale count
(hindsight showed 26.5k stars while GitHub had 34.5k). Batch at 100; probed
live: 317/317 repos in 4 requests, no errors.
2026-09-27 03:34:56 -07:00
teknium1 e2abb460b6 fix(website): put the repo root on sys.path before importing hermes_yaml in the stars probe
The scheduled Build Skills Index run executes
`python website/scripts/fetch-plugin-stars.py --probe`, so sys.path[0] is
website/scripts and the repo-root `hermes_yaml` shim it now imports is not
importable. Every scheduled probe since 2026-09-24 died with
ModuleNotFoundError, the live plugin-stars.json froze at that date, and the
41 catalog entries added since have no star count: they sort to the bottom of
'Most starred' and show no star pill regardless of real stars.

extract-plugins.py already inserts REPO_ROOT; do the same here.
2026-09-27 03:34:56 -07:00
brooklyn! e47db9414a feat(catalog): restore website skill links and native browse guidance 2026-09-25 12:09:55 -05:00
ethernet 4b7229d612 fix(icons): commit generated icons; installs and regular builds never render
User installs failed with 'resvg-py is missing' because the web/desktop
source builds rendered icons on whatever python was on PATH. The default
brand outputs are now committed; source_build, apps/desktop build.mjs and
the npm/docusaurus pre-hooks consume them directly. Flavored release
bundles (canary/commit) still render into their own product dir.

icons-freshness-check now regenerates and fails on any byte diff.
2026-09-24 19:17:34 -04:00
ethernet dcd2bca06a fix(desktop): render icons with core runtime dependencies 2026-09-24 01:30:15 -04:00
ethernet c13287c915 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/electron/main.ts
#	hermes_cli/backup.py
#	hermes_cli/config.py
#	hermes_cli/plugin_catalog.py
#	hermes_cli/plugins_cmd.py
#	hermes_cli/plugins_cmd_catalog.py
#	hermes_cli/plugins_discovery.py
#	hermes_cli/profiles.py
#	hermes_cli/update_cmd_deps.py
#	pyproject.toml
#	tests/gateway/test_dm_topics.py
#	tests/hermes_cli/test_config.py
#	tests/hermes_cli/test_plugins_cmd.py
#	tests/hermes_cli/test_update_autostash.py
#	tests/tools/test_lazy_deps.py
#	tools/lazy_deps.py
#	tools/skill_ledger.py
#	utils.py
#	website/docs/user-guide/security.md
2026-09-22 05:16:50 -04:00
teknium1 c3021aff0d ci(skills-index): re-mint the App token before the catalog stars probe; keep fetched_at honest on failure
The `Probe plugin catalog stars` step reused the GitHub App installation token
minted at job start. That token lives 1 h; `build_skills_index.py` (the step
before it) has walked ClawHub for 60-85 min since Sep 17, so every scheduled
probe since Sep 18 ran on an expired token and logged
`GraphQL probe failed (HTTP Error 401: Unauthorized); keeping previous counts`.
114 of the 213 catalog repos ended up without a star count and the "Most
starred" default sort fell to alphabetical for half the catalog.

Run-log evidence (job step timestamps; `gh run view --json jobs` + job logs):
  run 35569229751 (Sep 21): mint 06:36:52Z -> probe 08:01:50Z (85 min) -> 401,
      "Probed 213 repos ... wrote 99 star counts"
  run 35426522555 (Sep 19): mint 06:24:52Z -> probe 07:39:59Z (75 min) -> 401
  run 35133575877 (Sep 16, last success): mint 18:18:39Z -> probe 18:40:12Z
      (21 min) -> "Probed 100 repos ... wrote 100 star counts"

Fix: a second `./.github/actions/get-app-token` step (`probe-token`)
immediately before the probe; the probe reads that token. Ordering stays (the
probe does not depend on the walk, but the artifact upload wants both files
and the step comment already documents the walk timing).

Freshness: `main()` stamped `fetched_at = now` whenever the written map
differed from the previous one, so a FAILED probe still moved the timestamp
forward and the catalog footer read "popularity ranking as of <today>" over
Sep-16 counts (live `plugin-stars.json`: fetched_at 2026-09-21 with Sep-16
data). `probe_stars` now returns `(stars, probed)`; `fetched_at` advances only
when GitHub actually answered, otherwise the previous timestamp is kept, a
`::warning::` names how many catalog repos have no count, and the summary line
says "Probe failed; wrote N cached star counts (as of <ts>)" instead of
"Probed N repos".

Local repro (3 catalog repos, cache from Sep 16 with 2 of them, GraphQL 401):
  before: fetched_at restamped to now; "Probed 3 repos ... wrote 2 star counts"
  after:  fetched_at stays 2026-09-16T18:40:16+00:00; "::warning::plugin star
          probe failed; reused 2 cached counts from 2026-09-16..., 1 of 3
          catalog repos have no star count"

Not taken from #118129: the per-repo REST fallback and the payload-shape
validation. The transport was never the problem (the same expired token 401s
on REST too), and one GraphQL request per run is the rate-limit rule this
script documents.

Fixes #118113
credit: @fangliquanflq #118129 (honest fetched_at on a failed probe + ::warning; slim redo)
2026-09-21 23:31:02 -07:00
ethernet 9ac6af3809 merge origin/main (28 commits) into ethie/pm-clean
main extracted the launchd backend and the setup wizard out of hermes_cli/gateway.py. The
eight launchd functions pm-clean had changed are ported into gateway_launchd.py in its
_gw() style: runtime_command/installation_command for the gateway argv (no VIRTUAL_ENV in
the plist, XML-escaped args), _prepare_service_launcher before every plist write,
utf-8-sig plist reads, and launchd_restart's refresh-first + bounded bootstrap revival.
The systemd service-unit cluster stays in the facade (no gateway_service_unit sibling).
backup.py takes main's browser_profiles backup-only exclusion on our profile_root_entry
shape. main.ts takes main's attach-first backend block with our explicit types.
2026-09-21 05:07:21 -04:00
teknium1 2463550c97 feat(website): plugin pages render the README from the pinned commit by default
The README section was gated on `readme: true` in the catalog YAML and no entry set it, so
all 222 plugin pages shipped without one. READMEs now render for every GitHub/GitLab entry
(fetched at the reviewed sha, subdir first then repo root, common casings and docs/README.md
as fallbacks); `readme: false` opts an entry out. Build proof: 222/222 READMEs rendered.
2026-09-21 01:15:33 -07:00
ethernet 9f2ba1b74d merge origin/main (779 commits) into ethie/pm-clean
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).

Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.

uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
2026-09-21 00:58:39 -04:00
teknium1 10c273813d feat(plugin-catalog): screenshots and readme entry fields for the plugin pages
Two optional, submitter-controlled fields on a catalog entry feed the
entry's own page at /docs/plugins/<name>:

- `screenshots:` — up to 6 https URLs on GitHub hosts (same host rule as
  `image`, so the site never fetches from third-party hosts and a raw URL
  pinned to the sha is as immutable as the code).
- `readme: true` — the docs build renders the README from the PINNED
  commit (raw.githubusercontent.com / gitlab.com raw at <sha>), never live
  content, so what a user reads is what the reviewer read.

Validator rejects malformed values (admission), the loader parses and
drops off-host screenshots with a warning (client), and the extractor
emits `screenshots`, `readme`, `readmeUrl` and a `maintainerSlug` for the
author pages. Tests on all three.
2026-09-20 20:43:13 -07:00
teknium1 4c2c20f6e7 feat(website): plugin catalog shows when each entry was added/updated and can sort by it
extract-plugins.py derives addedAt/updatedAt per entry from one `git log
--name-status` over plugin-catalog/ (committer dates, so rebase-merged PRs
are stamped when they land; renames carry the original addedAt). A shallow
checkout or missing git yields null dates instead of wrong ones, and
deploy-site.yml now checks out with fetch-depth: 0 so the deploy has the
history.

The /docs/plugins page gains a Sort control (Most starred / Newest /
Recently updated) and an "Added … · Updated …" line on every card; Updated
is hidden while it equals Added.
2026-09-20 20:18:58 -07:00
teknium1 1536e75bfe docs(website): generator prunes stale skill pages; CI fails when the committed docs drift
`generate-skill-docs.py` now deletes every page under `user-guide/skills/{bundled,optional}`
it did not write this run, together with the zh-Hans mirror twin, so a skill that moves,
merges or leaves the shipped set takes its page with it instead of lingering as an orphan
that cross-links still reach (24 such pages after the shipped-set slim, plus 21 zh-Hans
copies whose English page was already gone).

The Docs Site Checks workflow regenerated the docs and never compared the result with the
committed copies, which is what GitHub renders; it now fails with a pointer to the generator
when they differ. Also regenerates the two pages that drifted since the salvaged commits.
2026-09-20 12:54:07 -07:00
ethernet 9f837d298b Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Conflicts resolved toward the PM model: main's lazy_deps/update_cmd_deps/npm
stamp machinery stays deleted (PM + scripts/build/node-deps.mjs own it), the
systemd ExecStop stop-mark rides the installation launcher, legacy
linux_only/macos_only/windows_only markers are rewritten to platforms(), and
finalize_update_receipt carries pending manual-serve obligations forward
again (lost when the ContextVar receipt rewrite crossed c0aa3ce354).

Test harness: the real-home I/O guard exempts /proc/<pid>/fd metadata reads
(deleted-WAL holder scans) and run_tests.sh drops ~/.hermes PATH entries so
shutil.which() cannot trip the tripwire.
2026-09-20 10:07:50 -04:00
teknium1 76fe8f7f5f fix: plugin catalog ranks by stars only and drops the review-claim badges
The catalog hero showed three trust chips ("Reviewed by the Hermes team",
"Installs exactly the version we reviewed", "One click from Hermes Desktop")
and the hero copy repeated the review claim. Teknium asked for the review
claim to go; the row goes with it since the remaining two chips only made
sense as a set.

Ranking pinned the official tier above every community entry, so a 5-star
official plugin sat above a 215-star community one and the page looked
unsorted. Tier no longer ranks; stars desc, unknown stars last, name as the
tiebreak. The Official/Community filter pills still exist for anyone who
wants the tier view.
2026-09-19 22:45:39 -07:00
ethernet d29da5fe20 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Conflicts resolved toward the branch: PM owns dependency preparation, the
Windows shim re-exec/hand-off path stays retired (main's shim-parent wait,
gateway-resume env token and update_cmd_deps tests dropped), docs describe
the PM update flow. The docker workflow parks install-stamp.json around the
toolchain step instead of deleting it so tests/docker can compare provenance.
2026-09-19 13:45:07 -04:00
teknium1 3eb9712180 Revert "feat(catalog): restore website install links to Desktop"
This reverts commit cf59980e2c.
2026-09-19 00:11:36 -07:00
ethernet 82a5affdd3 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	hermes_cli/backup.py
#	tests/hermes_cli/test_gateway_restart_loop.py
#	website/docs/developer-guide/web-search-provider-plugin.md
#	website/docs/getting-started/installation.md
#	website/docs/getting-started/updating.md
#	website/docs/index.mdx
#	website/docs/reference/cli-commands.md
#	website/docs/user-guide/docker.md
#	website/docs/user-guide/windows-wsl-quickstart.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/plugins/index.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/web-search-provider-plugin.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/index.mdx
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/cli-commands.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/docker.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/plugins.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md
#	website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/windows-wsl-quickstart.md
2026-09-18 18:41:16 -04:00
teknium1 36fb2be926 fix(website): cross-page doc links resolve on GitHub as well as on the site
Docs under website/docs were linked with Docusaurus site routes
(`](/getting-started/installation)`, `](/docs/user-guide/x)`), which GitHub's
file viewer resolves as repository paths and 404s (#114428). Relative Markdown
file links (`](../user-guide/x.md#anchor)`) are followed by both GitHub and
Docusaurus, so that becomes the authoring convention:

- website/scripts/check_doc_links.py lints hand-authored EN + zh-Hans pages
  for route-style links (`--fix` rewrites them, refusing any route that maps
  to no doc file); wired into the Docs Site Checks workflow and
  tests/website/test_check_doc_links.py.
- generate-skill-docs.py emits the same relative form for related-skill and
  catalog links instead of `/docs/user-guide/skills/...`.
- src/remark/relativeDocLinks.js rewrites `./x.md`/`../x.md` to
  content-root-absolute `/x.md` before Docusaurus resolves links, so a
  relative link still resolves in the zh-Hans build when source and target
  sit on different sides of the translation fallback (Docusaurus resolves
  `./`/`../` only against the source file's own directory).
- website/README.md states the convention and points at the checker.
2026-09-18 14:27:04 -07:00
ethernet b4a294fff9 Merge origin/main; keep PM as plugin dependency owner
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
2026-09-17 13:52:05 -04:00
teknium1 034313e7cd feat: plugin catalog entries carry an optional version label and card image
The 40-hex sha stays the release, but nobody reads one. Entries may now add
`version: "1.4.0"` (free-form, <=32 chars, never parsed) and `image:` (an https
URL on raw.githubusercontent.com / github.com / *.githubusercontent.com).

Why GitHub-only: the Desktop catalog browser deliberately never fetches from
third-party hosts, and a raw URL pinned to the entry commit is as immutable as
the sha it decorates.

Readers updated together: PluginCatalogEntry + entry_from_mapping (drop with a
warning, entry survives), validate_plugin_catalog.py (admission error), the
site extractor (drop, never fatal), the /docs/plugins card (banner + version
pill + "1.4.0 @ abcd1234" pin), the CLI table/info (pin_label), the TUI-gateway
plugin row (catalog_version -> Desktop "Update to 1.4.0"), and the Desktop
catalog detail header (image).
2026-09-16 14:18:39 -07:00
brooklyn! cf59980e2c feat(catalog): restore website install links to Desktop 2026-09-16 13:55:13 -05:00
teknium1 9796235822 Revert "feat(catalog): open website installs in Hermes Desktop"
This reverts commit b0e78e60fb.
2026-09-16 09:25:14 -07:00
brooklyn! b0e78e60fb feat(catalog): open website installs in Hermes Desktop 2026-09-16 04:33:07 -05:00
teknium1 471d8a427a ci(plugin-catalog): probe stars only from the scheduled skills-index run, in one GraphQL request
Aligns the star ranking with the rule the skills index already follows:
GitHub is consulted only by the twice-daily skills-index.yml schedule, whose
artifact every docs deploy reuses. deploy-site.yml now runs
fetch-plugin-stars.py without --probe (reuse-only: artifact → live site copy →
disk → empty) and cannot call the API at all, so a same-day merge train adds
zero requests regardless of cache age.

The probe itself collapses from one REST call per repo (~26 today, growing
with the catalog) to a single GraphQL query with aliased repository fields,
so the scheduled run costs one request no matter how big the catalog gets. A
failed probe (rate limit, renamed repo, bad token) keeps the previous counts.
2026-09-15 04:39:38 -07:00
teknium1 3e2e2c50eb feat(plugin-catalog): rank entries by GitHub stars, probed at most once a day
Catalog entries sort official → stars desc → name, both in browse shelves and
filtered grids, with a ★ pill on each card linking to the repo's stargazers.

Rate-limit discipline is the design constraint: the docs site deploys many
times a day and shares one GitHub App API budget with every other workflow
(tonight's merge train got rate-limited on unrelated uploads). So
website/scripts/fetch-plugin-stars.py first fetches the live site's own
plugin-stars.json (a CDN GET, not the API); if that cache is under 24h old it
is reused verbatim and GitHub is never called. Only a stale cache triggers one
GET /repos/{owner}/{repo} per unique catalog repo, and a 403/429 mid-run keeps
the previous counts instead of zeroing them. extract-plugins.py merges the
cache into plugins.json (`stars`) and plugins-meta.json (`starsFetchedAt`), and
the page footnote says when the ranking was last refreshed.
2026-09-14 21:24:33 -07:00
teknium1 2de17e5d40 feat(plugin-catalog): default shelf is Desktop, catch-all is General; categorise today's six entries
Teknium's call: most community submissions are Desktop panes, so an entry
without a category lands on the Desktop shelf; "other" becomes "general" for
plugins that genuinely span areas. Shelf order puts Desktop first. The six
entries merged today (pets-all, newswire, auto-titler, live-voice,
metamask-wallet, web-octen) get explicit categories.
2026-09-14 21:00:29 -07:00
teknium1 55dbd7f6e1 feat(plugin-catalog): shelve the catalog by category (Memory, Desktop, Platforms, …)
The catalog page was one undifferentiated grid filtered only by tier, so a
memory provider sat between two Desktop panes. Entries now carry an optional
``category`` (memory | desktop | platform | web | tools | voice | automation |
models | other, default other) that the loader, the admission validator and
the site extractor all understand.

/docs/plugins renders one shelf per category in browse mode, a category pill
row under the tier pills, a clickable category chip on every card, and a
results bar (active category, count, clear) when a filter or search flattens
the view. ``hermes plugins catalog`` gains a Category column and groups by it.
All 18 shipped entries are categorised. Unknown categories fail admission
(same contract as tier) so a typo cannot create a phantom shelf.
2026-09-14 21:00:29 -07:00