Registered Markdown, HTML, and downloadable file pages for zylos
- Zero-build publishing — write a
.mdfile, register it, it's a web page - Beautiful rendering — GitHub-style theme with dark/light mode
- Code highlighting — VS Code quality syntax highlighting via shiki
- Fast — LRU cache + singleflight dedup + file-watch invalidation
- Navigation sidebar — slide-out pages list for quick article switching
- Table of Contents — independent scrolling TOC for long documents
- Attachment pages — register and safely share arbitrary local files without rendering their bytes
zylos add pagesOr manually:
cd ~/zylos/.claude/skills
git clone https://github.com/zylos-ai/zylos-pages.git pages
cd pages && npm installPAGES_DIR="$HOME/zylos/.claude/skills/pages"
# Content root = contentDir in ~/zylos/components/pages/config.json
CONTENT_DIR="$(node -p "require(process.env.HOME+'/zylos/components/pages/config.json').contentDir || process.env.HOME+'/zylos/http/public/pages'")"
# 1. Write a page
echo "# Hello World" > "$CONTENT_DIR/hello.md"
# 2. Register it — pages are only served after registration
node "$PAGES_DIR/src/cli/pages.js" register \
--source "$CONTENT_DIR/hello.md" --uri hello --title "Hello World"
# 3. Visit https://your-domain/pages/p/hello
# Or browse all pages at https://your-domain/pages/To publish a file as a download page, register it through the same allowed-root
gate. Files other than matching .md/.html sources become attachment
pages; their landing page is /pages/p/<uri> and the owner download endpoint
is /pages/p/<uri>/download. A share keeps the same /pages/s/<token> ledger
and downloads through /pages/s/<token>/download.
node "$PAGES_DIR/src/cli/pages.js" register \
--source /absolute/resume.pdf --uri resumes/howard --title "Howard's resume"Attachment bytes are never sent through Markdown/HTML rendering, raw/state,
embedded-photo, or logical-asset APIs. Downloads use the extension MIME
allowlist (otherwise application/octet-stream), always force
Content-Disposition: attachment, and are no-store.
---
title: My Report
description: Q1 competitive analysis
date: 2026-03-21
tags: [research, competitive]
toc: true
---The page_id schema migration is one-way for older Pages binaries: after it
runs, reverting only the code makes the old binary fail against the re-keyed
database. Take a consistent backup before starting the new version. A
SQLite .backup includes committed WAL contents; copy attachment storage as
well because attachment directories are also renamed during this migration:
pages_data_dir=~/zylos/components/pages
pages_backup_dir="$(mktemp -d "${TMPDIR:-/tmp}/zylos-pages-pre-page-id.XXXXXX")"
sqlite3 "$pages_data_dir/pages.db" ".backup '$pages_backup_dir/pages.db'"
cp -a "$pages_data_dir/attachments" "$pages_backup_dir/attachments"Keep that backup until the migration has passed acceptance. Start the new version so the one-time migration runs, then verify the database and attachment storage before retiring the migration window:
PAGES_DATA_DIR=~/zylos/components/pages npm run verify-migration -- --jsonThe command is read-only and exits 0 only when the database schema, page
references, state keys, attachment metadata/files, and migration snapshots all
converge. It exits 1 with machine-readable failures for orphan or duplicate
rows, missing/mismatched/untracked files, legacy URI directories, or residual
snapshot tables. If a state snapshot remains, preserve it as retry evidence;
do not manually drop it. This is a post-migration acceptance check, not a
perpetual naming-convention check or an HTML scan.
If the upgrade must be rolled back, stop Pages first, restore both pages.db
and attachments/ from the pre-migration backup, then revert the code and
start Pages again. Do not revert the code against the migrated database.
Confirm the restored old version starts and serves the expected page/state
data before discarding the backup.
Pages includes a process-local, one-time form for general human-to-agent
delivery of short-lived sensitive values. It is zero-retention and fails closed
on restart. Separate local clients reach the daemon over a 0600 Unix socket;
no management endpoint is exposed through Caddy. Stripped-proxy deployments
use browser Fetch Metadata for same-origin checks; set an absolute
publicBaseUrl as the fallback for clients that omit it. See the lifecycle
and integration guidance.
Edit ~/zylos/components/pages/config.json:
{
"enabled": true,
"port": 3462,
"publicBaseUrl": "https://agent.example/pages",
"contentDir": "~/zylos/http/public/pages",
"theme": { "colorScheme": "auto", "codeTheme": "github-dark" },
"cache": { "enabled": true, "maxEntries": 200, "ttlSeconds": 3600 },
"security": {
"allowRawHtml": false,
"maxFileSizeBytes": 1048576,
"maxAttachmentSizeBytes": 52428800
},
"sharing": {
"enabled": true,
"passwordKeyFile": "/secure/pages/share-password-keys.json",
"passwordRateLimit": { "windowMs": 60000, "tokenMax": 8, "ipMax": 24 }
},
"state": {
"maxKeysPerPage": 50,
"maxPageBytes": 1048576,
"shareWriteRateLimit": { "windowMs": 60000, "max": 12, "ipMax": 30 }
}
}The state ceilings and write-rate limits apply only to share-link visitors;
authenticated owner writes are intentionally exempt.
security.maxFileSizeBytes remains the render ceiling. The separate
security.maxAttachmentSizeBytes ceiling applies to attachment registration
and download (default 50 MiB); install and upgrade hooks add it only when the
key is absent and preserve configured values.
The install and upgrade hooks make custody work out of the box: when no
keyring path is configured, they set sharing.passwordKeyFile to a default
outside the Pages data directory
(~/zylos/vault/credentials/pages/share-password-keys.json) and create the
versioned 0600 keyring there. To place the keyring yourself, configure
sharing.passwordKeyFile (or PAGES_SHARE_PASSWORD_KEY_FILE) before
install/upgrade, or at any time run:
node src/cli/pages.js share-password keyring initIf a configured keyring file is missing, the hooks deliberately do not recreate it — that is the lost-keyring scenario described below and it fails closed until operator recovery.
Do not copy key bytes into config.json. Back up pages.db and the keyring as
a consistent pair: the database alone can still verify recipient passwords but
cannot reveal them; the keyring alone contains no passwords. If the keyring is
lost, existing recipients can still unlock from the stored verification hash,
but create/rotate/reveal fail closed until operator recovery. To rotate, take a
fresh pair backup, run share-password keyring rotate, verify reveal, and only
then retire an unreferenced old key with share-password keyring retire <id>.
The service never silently regenerates a missing or malformed keyring.
Owner lifecycle APIs are CSRF-protected and use explicit operations:
POST /api/sharewithprotection: {"type":"password","mode":"generated"}ormode:"provided"plus a password in the JSON body.POST /api/share/:tokenId/password/enable,/reveal, or/rotate.DELETE /api/share/:tokenId/passwordto remove protection.
Create/enable/rotate/reveal responses are no-store; list responses include
only protection.type and retrievability metadata. Agents may read protected
HTML or Markdown with X-Zylos-Share-Password on GET/HEAD /s/<token> or
/s/<token>.md. The header never grants attachment or state writes. Never put
passwords in URLs, argv, logs, shared documents, Issues, Task comments, PRs, or
group chats; use stdin for provided CLI passwords and deliver secrets through
the intended private channel only.
A browser may keep up to 16 independently unlocked share sessions for one
Pages mount, within a 4096-byte recognized-cookie budget. Opening one share
does not authorize or overwrite another; when either limit is exceeded, Pages
expires the least-recently-used grant and removes its server-side session.
Owner authentication always takes precedence, and successful owner login or
logout clears every presented share session. Existing singleton
__Secure-share_access sessions are accepted only for their remaining
one-hour session lifetime and rotate to the per-share cookie form on use or on
the next unlock; share URLs, passwords, expiry, and revocation state do not
change.
Zylos is the open-source core of Coco — the AI employee platform.

