Skip to content

Latest commit

 

History

72 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Schaffa

Schaffa is the workhorse that connects an AI agent's output to the web. Its name comes from the Swabian word for working or getting things done. The self-hosted service publishes standalone HTML pages and public files from one origin.

Schaffa is heavily inspired by PostPlan and UploadThing, but built to be self-hosted.

What it does

  • Publishes complete HTML files under random 16-character slugs.
  • Keeps immutable page versions while /p/:slug always serves the latest.
  • Returns byte-identical HTML for static pages; interactive pages use a warning screen and isolated run URL.
  • Publishes files under random 128-bit IDs without retaining original filenames.
  • Records guides incrementally with optimistic concurrency, idempotent steps, cleaned screenshots, preflight checks, and immutable revisions.
  • Publishes script-free Marp presentations with optional PDF/PPTX/source artifacts through the CLI.
  • Converts images to metadata-free WebP, limits them to 2560 px and preserves transparency.
  • Accepts new anonymous HTML pages for one hour; tokens make pages permanent and enable files or updates.
  • Lets users sign in through Shoo and issue revocable upload tokens for their own agents.
  • Lets administrators enable sandboxed interactive pages only for explicitly trusted users.
  • Returns a stable URL immediately, scans page/file uploads asynchronously, and never exposes unscanned bytes.

Pangolin access model

Everything is served from one origin and one container on port 3000:

URL Access
https://schaffa.dev/admin Pangolin login, then a Schaffa admin token
https://schaffa.dev/api/* Direct access; writes and management require bearer tokens
https://schaffa.dev/skills, /skills/*/SKILL.md, /llm.txt, and /llms.txt Direct public access for agent examples and discovery
https://schaffa.dev/p/* and /f/* Direct public access

Configure Pangolin path rules to bypass its login for the supported /api/*, /skills, /skills/*/*, /llm.txt, /llms.txt, /p/*, and /f/* routes. All paths use the same hostname; Schaffa rejects application traffic sent with a different host.

See Deployment for the complete routing and runtime configuration.

Local test

The browser integration test uses a local Chrome, Edge, or Chromium installation. Without one, local test runs report that case as skipped; the remaining tests still run. CI and an explicit SCHAFFA_TEST_BROWSER path require a working browser and fail if it is missing.

Node 24+ and pnpm are required for the server and local development. The standalone CLI requires Node 22.12.0+:

pnpm install --frozen-lockfile
pnpm dev

pnpm dev is the only normal local entry point. It starts all configured services through Portless and prints their stable .localhost URLs. Open /admin on the printed Schaffa URL and sign in with the temporary token.

In a second terminal, use that printed URL and token:

export SCHAFFA_URL="<Portless URL printed by pnpm dev>"
export SCHAFFA_TOKEN="sfa_…"

skills/schaffa-publish/scripts/publish.sh page examples/hello.html
skills/schaffa-publish/scripts/publish.sh file examples/test-asset.png

CLI

The separately publishable npm package lives in packages/cli and is available from npmjs.org:

npx schaffa upload ./plan.html

Record a workflow while it happens:

npx schaffa record --title "Create a project" --chrome "https://app.example.com/projects"
npx schaffa record --title "Configure Calculator" --desktop --app com.apple.calculator

Chrome mode opens a new window in the already running Google Chrome without creating a profile. Chrome uses one of its existing profile sessions, so that session's logins, extensions, and password manager remain available. It records only that exact macOS window. The isolated --browser mode remains available when a separate persistent Schaffa browser profile is wanted. Desktop mode records only the native macOS app selected by bundle ID and needs one-time Accessibility and Screen Recording permission. Every primary click is captured before the UI changes, marked with a compact cursor and red target outline, saved locally under .schaffa/recordings/<slug>/, and uploaded immediately as an incremental guide step. Close the recorded window or press Ctrl+C to stop. Alt+Shift+R pauses capture for private screens. If the network fails, the local manifest remains recoverable with npx schaffa guide sync.

Manual recording remains available for terminal, API, and mixed workflows:

npx schaffa guide start --title "Create a project" --url "https://app.example.com/projects"
npx schaffa guide step --title "Open projects" --text "Open the project list."
npx schaffa guide finish

Inspect and correct an active recording before finishing it:

npx schaffa guide status --json
npx schaffa guide edit-step --step 2 --title "Choose New project" --text "Select New project."
npx schaffa guide replace-screenshot --step 2 --screenshot ./correct-step.png
npx schaffa guide delete-step --step 3

replace-screenshot cleans the supplied image, but it cannot recreate a recorder cursor or target outline. Those annotations are baked into the original screenshot pixels, so add them to the replacement image first when they matter.

Finishing a recording publishes it automatically. Corrections made after that point immediately create a new immutable public revision.

Publish a Marp presentation and its export artifacts:

npx schaffa publish deck.md --kind presentation --export pdf --export pptx

Requested PDF and PowerPoint exports appear as download links in the published presentation. Omit either --export option when that format should not be generated.

The CLI defaults to https://schaffa.dev. Every new HTML page receives a random, non-semantic ID. New HTML pages work without a token and disappear after one hour. For permanent pages, files, presentations, and guides, the CLI automatically reads SCHAFFA_TOKEN, local .env.local and .env files, and Schaffa token config files. An explicit --token <token> takes precedence. Use --ignore-token to skip token lookup and publish an anonymous HTML page.

Trusted users can create a separate Interactive token in their account and publish inline JavaScript with npx schaffa upload ./plan.html --interactive. Visitors see a warning before the code runs in an opaque browser sandbox. CSP blocks fetch requests and external resources; the sandbox restricts storage, forms, and pop-ups. Browser-dependent navigation and WebRTC behavior mean this is not complete network isolation.

Releases

Pushes and pull requests run CI without publishing. A semantic version tag such as v0.10.0 publishes the matching CLI package to npmjs.org and an immutable GHCR container image. Create the GitHub release with reviewed notes; the tag job attaches the CLI tarball, checksum, and image digest:

docker pull ghcr.io/beastyrabbit/schaffa:0.10.0

Production deployments should pin the digest recorded in the GitHub release.

Documentation

Run all project checks with pnpm check.

About

The workhorse that connects AI agent output to the web

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages