docs GitHub

Contributing

Writing the docs

docs.vyre.run is built from the Markdown files under docs/ in the Vyre repository. People read the rendered pages; agents read the same files raw. So every page follows one contract, and npm test fails when a page breaks it. This page is that contract. The decision behind it is ADR 0019.

Every feature merges with its doc page. A feature is not done until the page that describes it is true, in the same change.

Where a page lives

A page is a .md file under docs/, in the folder of its section: get-started/, using/, concepts/, build/, reference/, architecture/, adr/, security/, contributing/. Its URL is its path without .md:

FilePageRaw Markdown
docs/index.md//index.md
docs/using/vault.md/using/vault/using/vault.md
docs/architecture/index.md/architecture//architecture/index.md

Two folders are internal and never published, whatever is in them: docs/proposals/ and docs/design/boards/. They are listed under unpublished in docs/nav.json. A published page may not link into them.

Front matter

Every published page starts with a front matter block, then one # H1 that matches the title:

---
title: Vault
summary: One sentence, what the reader gets from this page.
audience: users, agents
owner: docs
status: stable
---

# Vault
KeyRequiredAllowed values
titleyesThe page's name, as the nav and the # H1 show it.
summaryyesOne sentence. It appears in search, in /llms.txt and under the title.
audienceyesA comma list from users, builders, operators, agents.
owneryesOne team: network, capsule-pro, capsule-sight, connectors, mobile, polish-cli, polish-surfaces, e2e, integrator, docs, planner, cc-plugin, glass-live, pwa. The owner keeps the page true (scripts/lib/docs/check.js holds the list).
statusyesstable (shipped on main), draft (partly shipped, may change), planned (not built yet).
generatednoThe script that writes the page. Only generated reference pages carry it.

Any other key is an error. The lists live in scripts/lib/docs/check.js (AUDIENCES, STATUSES, OWNERS); change them there, not here only.

The front matter parser reads a small subset of YAML: key: value, optional quotes, a trailing # comment after whitespace, and inline [a, b] lists.

The nav

docs/nav.json lists every published page, in order, by section:

{
  "site": { "title": "Vyre docs", "url": "https://docs.vyre.run", "repo": "https://github.com/vyre-ai/vyre" },
  "sections": [
    { "title": "Start here", "pages": ["index.md"] },
    { "title": "Get started", "pages": ["get-started/install.md", "get-started/first-day.md"] }
  ],
  "unpublished": ["work/", "proposals/", "design/boards/"]
}

Only pages in the nav are published. A published page that is not in the nav fails the check, and so does a nav entry with no file, a page listed twice, or a nav entry in an unpublished folder. The order in the nav is the order of the sidebar, the previous and next links, /llms.txt and /llms-full.txt.

Moving a page: redirect stubs

When a page moves, leave a stub at the old path with only a title and a redirect:

---
title: Specification
redirect: architecture/spec.md
---

The redirect is a docs-relative path. The build turns each stub into two 301s in _redirects, one for the pretty URL and one for the .md, and a link to the old path is rewritten to the new page. A stub is not listed in the nav (list its target), may not point into an unpublished folder, and may not point at another stub.

  • Link between pages with relative .md paths: [the vault](../using/vault.md#share-a-secret). The build rewrites them to page URLs.
  • Every link and every #anchor must resolve. Anchors are GitHub-style heading slugs: lowercase, punctuation dropped, spaces to hyphens, -1, -2 for repeats. ## 11. The security floor is #11-the-security-floor.
  • A link to a file elsewhere in the repository (../../core/vault/index.js) becomes a link to that file on GitHub.
  • Absolute paths on the site (/llms.txt, /using/vault) are checked against what the site serves. External links are not checked.
  • Link to the generated reference pages rather than repeating their tables.

Includes

A line holding only an HTML comment that names a file splices that file into the page:

<!-- include: ../CHANGELOG.md -->

Inside fenced code, as above, the line is an example and is left alone.

The path is relative to the page and must stay inside the repository. The included file's front matter is dropped, and links inside it resolve from its own folder. Includes nest a few levels deep. The raw .md the site serves has the include already spliced in. The check scans included files for the same characters and hygiene problems as pages, and fails on an include whose file does not exist.

Page syntax

Beyond plain Markdown, pages have a few blocks of their own. Every one renders to useful static HTML with JavaScript off; the script only adds to it. Raw HTML stays forbidden.

Commands and what they print

Every fenced block gets a Copy button. In an sh or console block, a leading $ is not copied, and in a console block that has prompts, lines without one are output and are not copied either. A command ending in \ carries on to the next line.

```console
$ vyre status
vyred is up
```

Put what the reader should see after a command in an output block. It is labelled "You should see" and has no Copy button.

```output
  Vyre is ready.
```

Tabs

For a choice of path or system. Without JavaScript each tab is a section with its label above it; with it, a tab bar. Picking a tab picks the same label in every tab group on the site, and the choice is remembered.

::: tabs
::: tab On a server
Markdown for this path. Headings (h3, h4) are fine and get anchors.
::: tab On this Mac
Markdown for this path.
:::

Use the same labels on every page (On a server, On this Mac) so one choice follows the reader.

Callouts

> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION] and > [!GAP] (shown as Known gap). Any of them may carry a title after the marker:

> [!WARNING] The HTTPS switch is off
> Wait for the certificate to finish, then press Check again.

> [!SNAG] is an "If this happens" box: the title (required) is what the reader sees, the body is what to do. It is never collapsed, and its title makes an anchor like a heading does, so troubleshooting can link to #the-page-says-not-found:

> [!SNAG] The page says "not found"
> Wait a minute for the certificate, then reload.

> [!WHY] answers a question for the curious, collapsed until clicked:

> [!WHY] Why does Vyre need a server?
> Your agents keep working while the Mac sleeps.

Screenshots

Keep shots next to the page in a shots/ folder (docs/get-started/shots/). An image alone in its paragraph becomes a figure, captioned by its alt text, or by its title when it has one. Write alt text that says what the screen shows.

![The address step, waiting for the certificate](shots/onboarding-address.png "Address")

If onboarding-address.dark.png sits beside it, the dark theme shows that file instead. The build reads each PNG's width and height from the file, so the page does not jump as shots load.

Shots are taken, not drawn. npm run docs:shots (on the test box, never the Mac) starts the sample world in a temp home and captures every shot listed in scripts/lib/docs/shots.js, in light and dark, with CHROME pointing at a headless Chrome. Each entry there names the source files the shot shows. docs/shots.json records a hash of those files at capture time, and docs-check warns about a shot once any of them changes, and fails on it only with --release, which scripts/release-check.sh runs, so a screen that moved on gets retaken before a release. An app change never turns another team's suite or stage red. To add a shot, add an entry to shots.js, run npm run docs:shots -- --only <name>, and put it on the page. Command output is text, not a picture: paste it into an output block (npm run docs:shots -- --cli prints the real output of the common commands from the sample world).

Demos

A widget that replaces its fallback when the page's script runs. The fallback is what readers without JavaScript (and agents reading the .md) get, so make it complete: a screenshot and a sentence, or a list of screenshots.

::: demo capsule
![Lumen, with results for north](shots/capsule-north.png)
Type in Lumen and it finds agents, projects, threads, notes and logins.
:::
  • capsule: a Lumen you can type into, with results from the sample world only.
  • onboarding: the fallback's list of screenshots, one per step, as Back and Next slides. Each step's name is its screenshot's title:
::: demo onboarding
1. ![Your name and a name for your assistant](shots/onboarding-you.png "You")
2. ![Sign in with Claude, or paste a key](shots/onboarding-claude.png "Claude Code")
:::

Only pages with a demo load the demo script. A name the site does not know fails the check.

Colours

A line holding only <!-- colors: dark --> or <!-- colors: light --> shows the palette from core/config/theme.js as a table of live swatches (token, value, use). The .md the site serves gets a Markdown table in its place. Change colours in theme.js (and TOKENS.md), not in a page.

Generated reference pages

docs/reference/{cli,tools,events,config,modules}.md are written from the code by scripts/gen-docs-reference:

PageSource
cli.mdthe command list in core/cli (what vyre help prints)
tools.mdevery ctx.tool definition, recorded by starting each module in a child process with a throwaway home
events.mdeach manifest's watches, with payload fields read from the emit calls
config.mdcore/config/index.js and every process.env.VYRE_ the shipped code reads
modules.mdevery module.json vyred would load

Do not edit them by hand. Change the code (a command's summary, a tool's description), then:

npm run docs:ref            # write the pages
node scripts/gen-docs-reference --check   # exit 1 if any page is stale, write nothing

The index

npm run docs:ref also writes docs/reference/index.md and docs/index.json (served at /index.json): every command and subcommand, tool, event, config key, VYRE_ variable, app screen and concept, with the code file that defines it, the page that explains it, and every page, line and heading anchor that mentions it. One lookup finds every instance of a thing. It reads every published page, so run npm run docs:ref after editing any page, not only after changing code; docs-check fails when the index is out of date.

docs-check also fails a stale mention: inline code or a sh/console command line naming a vyre command or subcommand, a tool, a config key under a known section, or a VYRE_ variable that the code no longer has. Placeholders (<name>), flags, file names and example modules are left alone, and ADRs, the changelog, known gaps and the spec are indexed but never failed. If a line must show an old or made-up name on purpose, end it with <!-- terms: ignore -->. Curated concepts live in CONCEPTS in scripts/lib/docs/terms.js; add one there, with the page and heading that explain it, when a page teaches a new word.

Check your pages

npm run docs:check                          # everything
node scripts/docs-check --no-reference      # skip regenerating the reference pages

It prints one path:line: problem per problem and exits 1 if there are any. It checks:

  • front matter: present, every required key, allowed values, no unknown keys;
  • nav: every published page listed, every entry a real file;
  • links: relative links, #anchors and docs.vyre.run paths resolve, nothing links into an unpublished folder;
  • redirect: stubs point at real, published, non-stub pages;
  • syntax: every ::: container is known and closed, a demo names a widget that exists, a [!SNAG] or [!WHY] has its title, a colors line says dark or light. Headings inside tabs and [!SNAG] titles count as anchors;
  • characters: no em dash and no section sign, in pages and in files they include;
  • hygiene: no real person's or business's name, nothing shaped like a secret, no email address outside the example domains, no IP address outside the documentation and private ranges (scripts/lib/hygiene.js);
  • reference: the generated pages and the index match what the code and the pages make now;
  • stale: no page names a command, tool, config key or variable the code no longer has;
  • shots: every screenshot is in docs/shots.json, and none is older than the code it shows.

test/docs-check.test.js runs the same check on the real tree under npm test, so a broken page fails the suite.

Build the site

npm run docs:build                   # into docs-site/ (gitignored)
node scripts/build-docs --out DIR    # somewhere else

scripts/build-docs empties the output folder, then writes:

  • each page as HTML at its pretty URL, and its Markdown source beside it at the same path with .md, front matter kept and includes spliced;
  • search-index.json for the search box: one item per page intro, heading and [!SNAG], each with its anchor, so a result jumps to the section. Press / to search;
  • llms.txt, an index of every page by section with its .md link and summary, and llms-full.txt, every page's Markdown in nav order in one file;
  • sitemap.xml, robots.txt, 404.html, favicon.svg;
  • _redirects (a 301 for every stub) and _headers (Markdown and text content types, cache rules) for Cloudflare Pages;
  • one stylesheet and one script under assets/, named by content hash, and the demo widgets' stylesheet and script, linked only from pages with a demo.

The same tree builds the same bytes: nothing reads the clock or the network. The renderer (scripts/lib/docs/markdown.js) has no dependencies. It renders CommonMark plus GFM tables, task lists, strikethrough and alerts (> [!NOTE], and > [!GAP], shown as Known gap), and the page syntax above. It does not render raw HTML: every < shows as text, so write <you>.vyre.run as it is. Indented code blocks are not supported; use fences.

The site deploys to the Cloudflare Pages project vyre-docs:

npx wrangler pages deploy docs-site --project-name vyre-docs --branch main

Style

  • No em dash and no section-sign character, anywhere. Use a colon, a comma, or two sentences. Write "Section 5.1" for spec references. The check enforces this.
  • Examples use the sample world only: the user alex, the firms Juniper Studio and Northwind Bakery, the assistant and agents juno and kit, the domains example.com, juniperstudio.example and *.example, names like alex.vyre.run, and addresses from 192.0.2.x or 100.64.x.x. The check enforces names, emails and addresses.
  • Plain, direct English, in the second person. Short paragraphs. Concrete commands in fenced blocks. No marketing adjectives, no "simply", no "seamless".
  • Write only what is true on main. Copy commands, flags, config keys, tool names and paths from the code. When something is designed but not built, say "Not built yet." or mark the page draft or planned.
  • A reader who is an agent should be able to act from the page alone.

A user page has this shape: one paragraph of what it is and why; the common tasks, with headings that are tasks ("Share a secret with an agent"); what it will not do; where to go next.

Known gaps

Document what the code does. When that differs from the spec, an ADR or a screen, say so on the page in a callout and give the gap its own section in known gaps, with what is true now, what to do instead, and the owning team:

> [!GAP]
> The pause switch pauses but does not resume. See [known gaps](../known-gaps.md#the-pause-switch-does-not-resume-a-watcher).

The change that closes a gap removes its callouts and its section.

A page template

---
title: Watchers
summary: Small programs Claude writes that watch something for you and file what they find into a project.
audience: users
owner: docs
status: draft
---

# Watchers

One paragraph: what a watcher is, and why you would want one.

## Write a watcher

Steps, with the exact commands:

    vyre watchers create <name>

## Pause one

...

## What watchers will not do

...

## Where to go next

- [Projects and threads](projects-and-threads.md)
- [Tools reference](../reference/tools.md)

(In a real page, fence the command with three backticks instead of indenting it; indented code is not rendered.)

Where to go next