Preview local HTML reports, Markdown docs, and static mini apps, then publish them to shareable Cloudflare Pages URLs — from the terminal or your coding agent.
Feature HTML: https://pagecasthq.pages.dev/
About
Pagecast is a local-first publishing tool for agent-generated reports and small
static web projects. Preview files, manage published versions, rename links,
re-sync updates, password-protect pages, and revoke old URLs — from a local
admin UI or headless pagecast commands.
The trust boundaries, state/target invariants, and corrected PR-lineage decisions are recorded in ARCHITECTURE.md.
Good fits: HTML reports and dashboards (Playwright, Lighthouse, coverage);
Markdown plans, docs, and release notes; static mini apps from dist/build/out;
coding-agent workflows that publish a finished artifact on request.
Not a fit: private scratch notes, or server-rendered apps that need a running backend (export static assets first).
Quick Start
Requires Node.js 20.19+ and a Cloudflare account for publishing. Run the exact Pagecast 0.5.0 release without a global install:
The versioned command is reproducible. Unversioned npx pagecast follows npm's
current latest tag. From a source checkout of main, run npm start instead.
This starts the local app and opens the admin UI:
- Admin UI —
http://pagecast.localhost:4173 - Local preview/public origin —
http://pagecast.localhost:4174(the admin embeds this separate origin; raw report HTML is never served by the admin origin) - Local data/config —
.pagecast/in the current directory
Pagecast state is workspace-scoped. Run the CLI, background service, and MCP server from the same directory to use the same reports and Cloudflare target.
Upgrading from 0.4 to 0.5
-
Stop any v0.4 foreground process with
Ctrl-C. For a transient background process, switch versions explicitly:npx pagecast@0.4.0 background stop npx pagecast@0.5.0 background start
-
If you installed the macOS login service, rerun
npx pagecast@0.5.0 background service install. The LaunchAgent captures the exact Node and CLI paths, so restarting the old service does not upgrade it. -
Replace the unpacked Chrome extension with the matching 0.5.0 release folder (or the
extension/folder from the same source checkout), then click Reload inchrome://extensions. The 0.5.0 extension negotiates the new admin-session token before publishing. -
Existing URLs, passwords, expiries, redirects, Cloudflare selections, and
.pagecast/data remain readable. Existing word-only URLs are preserved as issued; Pagecast does not silently rotate them. -
A legacy publication whose Cloudflare account/project was never recorded is readable but cannot be synced, renamed, re-expired, or revoked until you select its original project and click Attach selected project for that link.
-
On a genuinely fresh install, telemetry sends nothing until you run
npx pagecast telemetry enable. Existing saved choices and explicit environment overrides keep their previous behavior.
Want Pagecast waiting in the background instead of keeping a terminal tab open?
npx pagecast background start npx pagecast open
On macOS, you can also make Pagecast feel like a tiny local app that survives login/restart and opens without a visible port:
npx pagecast setup-local-url
That installs a local-only pf redirect for http://pagecast.localhost and a
user LaunchAgent that keeps the Pagecast server running after login. Pagecast
still runs internally on its normal port; it does not run the app as root or
occupy port 80 with a server process. Manage the pieces separately with:
npx pagecast local-url status npx pagecast local-url remove npx pagecast background service status npx pagecast background service uninstall
Pagecast remembers the local ports in .pagecast/config.json. If 4173/4174 are
busy the first time it starts, it falls forward to the next available pair and
keeps using that pair on future launches. The bundled extension discovers that
exact origin from the dashboard tab opened by Pagecast and remembers it; it does
not scan localhost ports. On first use of a fallback port, keep that dashboard
tab open long enough for discovery. The extension supports Pagecast's standard
pagecast.localhost, localhost, and 127.0.0.1 hosts; a server deliberately
bound to another loopback address (for example 127.0.0.2 or ::1) still works
through the CLI/dashboard but is outside the extension's host permissions.
In the admin UI, click Connect Cloudflare. Pagecast uses scoped Wrangler
OAuth (account:read, user:read, pages:write), detects your account, and
creates the Pages project if needed. Selecting an existing project is the
explicit signal that this workspace may manage its /p/... publications;
Pagecast will not silently adopt an unrelated project. From a clone, run
npm start.
Prefer containers? Pagecast ships with Docker support — see Run with Docker.
Prefer the terminal?
npx pagecast pages setup --project pagecast # multiple accounts? add --account <account-id> # automation? export CLOUDFLARE_API_TOKEN (scoped Pages:Edit) + CLOUDFLARE_ACCOUNT_ID
Publish From The Terminal
# An HTML or Markdown file → a memorable /p/<slug>/ link (source folder included) npx pagecast publish "/absolute/path/report.html" --json # Set an expiry — 7d, 12h, or never (default 30d) npx pagecast publish "/absolute/path/report.html" --expires 7d --json # A built static project → publish its entry file npm run build && npx pagecast publish ./dist/index.html --json # A whole folder → replace a named Pages project directly (--branch defaults to main) npx pagecast pages deploy ./dist --project my-static-site --json
The CLI's pagecast publish copies every non-hidden, non-symlink file under its
source folder, even when the entry HTML does not reference it. Every copied file
is reachable beneath the published URL, so publish from a clean folder and keep
credentials and source secrets elsewhere. MCP publish_file isolates the entry
file unless sibling assets are explicitly confirmed.
New links are unlisted by default: a memorable label plus a 128-bit opaque
capability (for example, /p/hollow-paperclip-<32-hex-characters>/). Anyone who
has the URL can view it; unlisted does not mean private. The admin UI's
Short public link toggle instead mints an intentionally easy-to-share,
guessable public drop. Renaming a link to a vanity slug without the 128-bit
suffix is also an explicit conversion to a public drop. Password protection is
the access-control option. Existing word-only links keep their original URL and
continue to resolve.
Pagecast records the actual production origin returned by Cloudflare rather
than assuming <project>.pages.dev, so a global hostname collision does not
leave links or social metadata pointing at the wrong host. Add --json for
agents and CI, and use the admin UI for link renaming, re-sync, revoke, and
build settings.
pages deploy is a separate whole-site operation. It replaces the contents of
the named Cloudflare Pages project and does not change the project selected for
managed /p/... publications. Use a separate project unless replacing the
managed publication site is intentional. Managed publication workflows record
incomplete Cloudflare/local reconciliation in the operation journal and surface
it for safe retry or manual action; direct pages deploy is stateless, so
inspect deployment history before retrying an ambiguous timeout.
Common errors: statusCode 401 means Cloudflare setup or authentication is
required. statusCode 409 means a conflict; follow the returned message. For
the explicit multiple-account conflict, run
npx pagecast pages setup --project pagecast --account <id>.
Password Protection
Gate any published page behind a password — from the admin UI (the Password protection toggle) or headlessly:
npx pagecast publish "/absolute/path/report.html" --password "your-password" --json npx pagecast publish "/absolute/path/report.html" --no-password --json # remove it
Enforced at the edge by a generated Cloudflare Pages Function, so a successful protected publish gates every file of a multi-file report from its first live deployment. Protection changes on an existing link take effect target by target only after each replacement deployment succeeds. Cloudflare's immutable URLs for older deployments are not retroactively gated; prune any historical unprotected deployment that must stop being reachable. Crypto, security model, and caveats: PASSWORD-PROTECTION.md.
Deploy History
Every publish or re-sync creates a new Cloudflare Pages deployment — an immutable
snapshot of your whole site at that moment, each with its own <hash>.pages.dev
URL. Over time these pile up. View and remove them from the admin UI
(Settings → Deploy history) or the terminal:
# List recent deployment snapshots (the newest production one is marked live) npx pagecast pages deployments list --json # Remove one snapshot by id (the live deployment is protected and can't be deleted) npx pagecast pages deployments delete <id> --json # Keep the N most recent (incl. live) and remove the rest npx pagecast pages deployments prune --keep 5 --yes --json
Snapshots are whole-site, not per-page, so removing one never affects your live
site or your pages in Pagecast — it just frees up old <hash>.pages.dev URLs.
That also means expiry, password changes, and revoke apply to the production
site's replacement deployment; an older immutable deployment URL keeps the
bytes and gate state it originally shipped with until that deployment is
deleted. Prune history when those direct snapshot URLs must stop working.
Use From Coding Agents
Pagecast ships a Codex-native skill and a portable Agent-Skills file that offer to publish finished artifacts — only after you confirm.
# Codex cp -R .codex/skills/publish-report ~/.codex/skills/ # Claude Code /plugin marketplace add Amal-David/pagecast /plugin install pagecast@pagecast # Any other agent cp plugin/skills/publish-report/SKILL.md /path/to/your-agent/skills/publish-report/SKILL.md
More detail in plugin/README.md.
Use Through MCP
Pagecast can run as a Model Context Protocol server, so MCP-capable agents can publish generated HTML/Markdown, inspect known pages, and revoke links through a bounded tool surface instead of shelling out to the CLI themselves.
For local agents, configure the stdio server:
{
"mcpServers": {
"pagecast": {
"command": "npx",
"args": ["pagecast", "mcp"]
}
}
}That command shares the same .pagecast/ config, Cloudflare credentials, expiry,
and password-protection behavior as npx pagecast publish. Add
"--data-dir", "/path/to/.pagecast" to the args only when you intentionally want
the MCP server to use a different Pagecast workspace.
MCP tools
| Tool | Purpose | Safety notes |
|---|---|---|
status |
Show Cloudflare/Pagecast connection state. | Redacted by default; pass verbose: true only for trusted local clients. |
list_pages |
List locally known reports and published links. | Redacted by default; pass verbose: true only when local paths/build settings are safe to reveal. |
publish_content |
Publish supplied HTML or Markdown content. | Preferred for agents; writes content into isolated Pagecast storage first. |
publish_file |
Publish a local .html, .htm, .md, or .markdown file. |
Isolates the entry file by default. To include sibling assets, pass both includeAssets: true and confirmAssets: true. |
revoke_publication |
Take a published token offline and redeploy the Pages site. | Requires confirm: true because it changes a live URL. |
Example publish_content tool arguments:
{
"content": "<h1>Quarterly report</h1>",
"filename": "quarterly-report.html",
"label": "Quarterly report",
"expires": "7d",
"password": "optional-page-password"
}Example publish_file with sibling assets intentionally included:
{
"path": "/absolute/path/report.html",
"label": "Report with assets",
"includeAssets": true,
"confirmAssets": true
}Organization and VPN deployments
The MCP server is intentionally separate from the admin API. The admin API can run local publish/build work and should stay loopback-only. Do not expose the admin port to a VPN or shared network.
This release supports local stdio MCP. An org-hosted HTTP MCP endpoint or VPN-only publishing target should be a separate hardening pass with an explicit auth model, per-user authorization, audit logging, rate limits, and a threat model for what the server is allowed to read and publish.
Putting a future MCP endpoint behind a VPN would control who can invoke Pagecast; it would not by itself make the returned published URL VPN-only. For restricted recipients today, use Pagecast password protection or protect the Pages/custom domain with your organization's access layer.
Run with Docker
A single image bundles the whole pagecast CLI, so it serves the admin dashboard
and runs every publish/deploy command — they are the same program.
# Serve the dashboard (build on first run, then open http://localhost:4173)
docker compose up --buildRelease images are published to GHCR:
docker pull ghcr.io/amal-david/pagecast:latest
docker run --rm \
-p 127.0.0.1:4173:4173 -p 127.0.0.1:4174:4174 \
-v "$PWD/.pagecast:/app/.pagecast" \
ghcr.io/amal-david/pagecast:latest serveThe local published-page preview is on http://localhost:4174, and your config +
publish history persist in ./.pagecast (mounted as a volume).
Publishing from a container uses an API token, not the dashboard's "Connect
Cloudflare" button — that button opens a browser OAuth flow a container can't
complete. Copy .env.example to .env, add a scoped token, and docker compose
picks it up:
cp .env.example .env # fill in CLOUDFLARE_API_TOKEN (+ CLOUDFLARE_ACCOUNT_ID)Run any command headlessly (CI, servers) from the published image — mount your working directory and pass the token through:
docker run --rm -v "$PWD:/work" -w /work \
-e CLOUDFLARE_API_TOKEN -e CLOUDFLARE_ACCOUNT_ID \
ghcr.io/amal-david/pagecast:latest publish ./report.html --jsonOr build locally when you want to test an unreleased checkout:
docker build -t pagecast .Notes:
- The admin server can run build and publish commands. Browser mutations use an
Origin check and per-process session token; every no-Origin mutation requires
the private workspace capability. These are not remote-user authentication.
Non-loopback binds are rejected, and the image's wildcard bind works only
because it explicitly selects loopback-proxy mode. Expose both ports on the
host's loopback only (
127.0.0.1). Withdocker run, use-p 127.0.0.1:4173:4173and-p 127.0.0.1:4174:4174, never bare-pmappings. - On Linux the container runs as uid 1000 (
node); if a bind-mounted./.pagecastisn't writable by that uid you'll get permission errors.mkdir -p .pagecastbeforecompose up(orchown 1000:1000 .pagecast) fixes it. Docker Desktop and OrbStack handle this automatically. - Wrangler is pinned to
4.86.0. Native and Docker publishing read the same pin fromsrc/platform.js; Docker bakes the global binary into the image and selects it directly, so runtime deploys do not contact npm. Native publishing invokes that exact package throughnpx. The exact-version-onlyPAGECAST_WRANGLER_VERSION_OVERRIDEis for tests and deliberate local compatibility work, not normal production use.
Chrome Extension (Experimental)
⚠️ Experimental — load-unpacked only, not yet on the Chrome Web Store.
When an agent opens an HTML file as file:///…/report.html, the bundled
extension adds a one-click Publish to Pagecast button (the running server
must be up). From a source checkout, install via chrome://extensions →
Developer mode → Load unpacked → select extension/, then enable
"Allow access to file URLs". npm does not install the extension directory;
npm/npx users should download pagecast-extension-v<version>.zip from the
matching GitHub release,
unzip it, and load that folder. See extension/README.md.
Admin UI Features
- Add
.html/.mdfiles by path orfile:///…URL, deployable static folders, or source folders with a build command and output directory. - Drag to reorder; publish, re-sync in place, rename links (old links redirect; vanity slugs are public drops), or revoke one/all versions.
- Auto-sync path-backed reports; password-protect pages; edit HTML in-app without touching the original source file.
- Review interrupted managed operations, retry safe recovery steps, and see when a conflict requires manual action.
- View deploy history and remove old whole-site snapshots (the live deploy is protected), with a one-click "keep newest N" cleanup.
Security Model
- Admin UI binds to loopback by default, rejects non-loopback Host headers, and requires same-origin browser mutations plus a per-process CSRF token.
- No-Origin admin mutations require the private workspace capability. Wildcard binds require explicit loopback-proxy mode and arbitrary routable bind hosts remain rejected.
- Draft/report HTML is served only by the separate preview origin (normally
port 4174). The admin origin redirects
/preview/...there and embeds it in a sandbox without top-navigation permission. - The Chrome extension is limited to its session/status/local-publish adapter routes and sends both an extension marker and the current session token.
- Production access is through active
/p/<slug>/links; revoked links 404 after the replacement deploy finishes. Direct URLs for older immutable Cloudflare deployments retain their original snapshot until pruned. - Public routes reject directory traversal and hidden files. CLI path publishing makes every regular non-hidden, non-symlink file under the report's source folder reachable, even if the entry HTML does not reference it — keep secrets out of that folder.
- The Pages root publishes no report listing.
Telemetry
Pagecast can collect anonymous usage stats — which command ran, the Pagecast/Node version, and OS/arch — to guide what gets built next. It never sends file contents, file paths, published URLs, or Cloudflare tokens/account IDs. See PRIVACY.md for the exact fields.
Fresh installs are consent-pending and send nothing. Enable it explicitly:
npx pagecast telemetry enable # inspect with: npx pagecast telemetry status npx pagecast telemetry disable # Explicit env overrides are also supported (DO_NOT_TRACK always wins). export PAGECAST_TELEMETRY=1 export PAGECAST_TELEMETRY=0 export DO_NOT_TRACK=1
Existing persisted choices remain compatible. Telemetry is automatically off
in CI unless explicitly re-enabled with PAGECAST_TELEMETRY=1.
Development
npm start # run the packaged app from source npm run check && npm test # verification suite npm run build # rebuild the React admin UI (web/) into public/
UI development and builds require pnpm 10. Install the web dependencies with
pnpm -C web install --frozen-lockfile --ignore-scripts, then run Vite with
pnpm -C web run dev (proxied to the server on 4173).
The root CLI/server has no runtime npm dependencies. Layout: src/ (CLI, server,
publisher), public/ (built UI), web/ (React source), plugin/ +
.codex/skills/ (agent skills), test/ (Node tests).
Source-folder build commands run through the platform's native command
interpreter: sh -lc on POSIX and %ComSpec% /d /s /c on Windows. Pagecast
selects the right shell, but it cannot translate command syntax; author a
portable command or configure a platform-appropriate one.
Contributing
Issues and pull requests are welcome.
-
Fork and branch from
main. -
Make your change. Keep the root CLI/server free of runtime npm dependencies.
-
Run the verification suite before opening a PR:
npm run check && npm test
-
Rebuild the admin UI if you touched anything under
web/:npm run build # regenerates public/ -
Open a PR describing what changed and why.
The merged workflow defines four Node matrix checks plus Build, package, and
container proof. A repository administrator still needs to configure branch
protection or a ruleset for main to make those checks required; workflow files
cannot enforce that repository setting.
See the Development section for the project layout. Please don't file public issues for security problems — report them privately via SECURITY.md.
License
MIT — see LICENSE.
