Tutorials
Everything you need to get going with Pupa — start with the basics, then learn to use, build, and share a MyApp. Written guides now; video walkthroughs are on the way.
Quick setup
The fastest path to a working Pupa: run the backend on your own machine with Claude Code, reach it over a free Cloudflare tunnel, install the app, pair, and start chatting. About five minutes. Every step has a fuller version in Backend setup below.
- Install the backend and run setup. Make sure the Claude Code CLI is installed and logged in first — the wizard checks for it — then:
In the wizard, enable the Claude Code harness, and when asked “How will your iPhone reach the backend?” pick Cloudflare tunnel and answer no to the domain question — that’s the free quick tunnel, no Cloudflare account needed.uv tool install "pupa-backend[setup]" pupa-backend setup - Start the backend. This serves on
:8004and brings the tunnel up:pupa-backend run - Install the app. Get Pupa on your iPhone or Mac via TestFlight (email pupa-app-help@proton.me to request an invite), or build it from source in Xcode — see the pupa repo.
- Pair, then chat. Mint a code and scan it from the app:
In Settings ▸ Backend ▸ Edit, scan the QR. That’s it — open a MyApp and start talking.pupa-backend pair
MyApp
A MyApp is the thing you make in Pupa. Here’s what one is, and how to use, build, and share it.
What is a MyApp?
A MyApp is an app you and your agent build and use together by talking. Think of it as a box of Lego blocks — a field, a list, a date, a button, a chart — that you snap together into whatever you need: a habit tracker, a calendar, a checklist, a calculator, or a room to hang out in.
You and the agent work on the same footing — anything one of you can do, the other can too. Usually you just describe what you want and the agent assembles the blocks in front of you, but you can also snap them together by hand, and either of you can rearrange them at any time.
- Chat-driven. You say what you want; the agent snaps the blocks together while you watch and edit the same view.
- It remembers. A long-lived Memories filesystem keeps what matters to you across sessions and devices.
- It’s portable. Any MyApp packs into a single
.pupabundle you can hand to anyone — it runs on their host, with their backend.
Use a MyApp
A MyApp is something you live in. You just talk to it, and the agent snaps the blocks into place while you watch and edit the same view.
- Open a MyApp from your library — each one blends whatever blocks it needs (trackers, calendars, checklists, charts, rooms) with its own Memories and agent setup.
- Type what you want in the side-panel chat: “log today’s run”, “add milk to the list”, “show me this month”.
- The blocks rearrange in front of you. Edit any field by hand too — you and the agent share one screen.
- It remembers. The long-lived Memories filesystem keeps what matters across sessions and devices.
Build a MyApp
Mould the canvas into the shape you need — no template to pick, just describe it and refine it in place.
- Start a new MyApp and tell the agent what it’s for.
- Ask for the pieces you need: trackers, calendars, checklists, calculators, charts, rooms — cross-linked into one coherent app.
- Iterate by talking. Rename, restructure, add fields; the blocks rearrange live.
- Everything persists in the Memories filesystem, so the app grows with you instead of resetting.

index.json anyone can host) for others to import.Open source
Contribute
Add new blocks — canvas components — to the app, or new agent hookups to the backend. Components are just SwiftUI views with a typed model, so you can build agent-compatible surfaces without any agentic knowledge. The client, backend, and marketplace are all developed in the open — see the repos for each project’s terms.
The native iOS / macOS application. Ships SwiftUI canvas, it's components, Memories filesystem and how those are surfaced to your backend harness.
The user-owned backend. Hooks-up on your Agent harness on your laptop (like Claude-Code). Run it locally next to the app or deploy your own in the cloud. Pair-once auth; your keys stay yours.
+---------------+ +--------------+ +-------------+ | Pupa app | | pupa-backend | | Claude Code | | (iOS / macOS) |<--->| (your laptop |<--->| (your agent | | | | or cloud) | | harness) | +---------------+ +--------------+ +-------------+
A catalog of .pupa bundles you can browse and install from the app. Just an HTTPS index.json anyone can host. App content is CC0; tooling is MIT.
Where your data lives
Pupa has no cloud of its own. Your apps run on your devices, sync through your iCloud, and talk to a backend you run — so your data stays with you, not with us.
- Your MyApps, memories, and settings live on your device and sync across your iPhone, iPad, and Mac through iCloud, under your own Apple ID. There’s no Pupa account and no Pupa server in the middle — the local copy is the source of truth, and iCloud is just the mirror that keeps your devices in step.
- Your agent and model credentials never leave your machine. The app pairs to a backend you run, so your API keys and logins sit in that backend’s environment; the paired-device token stays in the iOS Keychain and is excluded from sync.
- Chat history lives in your backend, not iCloud — kept in the database you point it at, yours to keep or wipe. Give the backend no database and history simply ends with the process.
- The one external hop is the model you pick. What you send the agent goes to your LLM provider (Anthropic, OpenRouter, …) under your own key — the same trust you already place in that provider, and nowhere else.

Run your own backend
Pupa is user-owned: the app pairs to a backend you run, and your model credentials never leave your machine. Pick a harness or model provider, install the CLI, pair, and you’re live. Full reference lives in the pupa-backend README.
Install the CLI (PyPI)
Install pupa-backend from PyPI with uv — it puts the CLI on your PATH in an isolated environment and is fast. Pin the version to match your app:
uv tool install "pupa-backend[setup]" # latestRun the setup wizard once, then start the server:
pupa-backend setup # interactive — writes ~/.pupa-backend/config.yml
pupa-backend run # serves on http://localhost:8004Prefer it always-on? Install it as a background service (logs go to ~/.pupa-backend/logs/).
pupa-backend service-install # install and start the background service
pupa-backend status # is it running?
pupa-backend logs # tail the service log
pupa-backend service-stop # stop and remove itCredentials live in your shell env (see the backend’s .env.example), never in the config file.
Option A · Claude Code (subscription)
Claude Code is a harness — a self-contained agent loop the backend runs on your existing Claude subscription instead of metered API tokens. Install the Claude Code CLI so the claude binary is on your PATH, then enable the harness in ~/.pupa-backend/config.yml:
harnesses:
claude_code:
enabled: trueEvery enabled harness is mounted separately, so the app shows a Claude Code option in its per-connection harness picker — no app update needed. It uses Claude Code’s native host tools, gated by the permission controls the app renders.
Option B · OpenRouter (300+ models)
Prefer metered API access or non-Claude models? Point the backend at OpenRouter, an OpenAI-compatible endpoint. Set these in your shell env:
LLM_PROVIDER=openai_compatible
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_API_KEY=sk-or-...
LLM_MODEL=anthropic/claude-sonnet-4.6Or set just OPENROUTER_API_KEY=sk-or-... to unlock the curated in-app model picker (GLM, Qwen, MiniMax, Kimi, DeepSeek) — the client chooses the model per request, no LLM_MODEL needed.
Connection channels
Cloudflare tunnel — a public HTTPS URL to your backend, reachable from anywhere with no ports to open.
“How will your iPhone reach the backend?” is the question in pupa-backend setup that picks your connection channel. It’s saved as connectivity in config.yml; re-run setup any time to change it. Three options, quietest to most reachable:
- Localhost — same machine only. The app talks to
http://localhost:8004and nothing leaves your computer. Ideal when the macOS app runs on the same Mac as the backend: no tunnel, no exposure. - Cloudflare tunnel — a public HTTPS URL to your backend with no ports to open. The free quick tunnel needs no domain and no account (the backend brings it up on
run), but its URL changes on every restart, so you re-pair each time. Own a domain on Cloudflare? Choose “I have a domain” for a named tunnel with a stable URL — pair once, never again. - Tailscale — a private mesh VPN across your own devices (free). You get a stable MagicDNS name and traffic stays private instead of being published to the internet; the wizard generates a self-signed TLS cert for it. Recommended for everyday remote use — needs Tailscale installed and running on both the backend host and your phone.
Whichever you pick, pairing is the same flow below — the channel just decides which URL the QR code embeds.
Pair the app
With the backend running, mint a pairing code and scan it from your device:
pupa-backend pair # prints a QR pairing codeIn the app, go to Settings ▸ Backend ▸ Edit and scan the QR (or paste the code). The app defaults to http://localhost:8004/; point it at a hosted instance from the same screen. Pairing is one-time — after that the app reconnects on its own.
Screen-share
Screen-share lets the agent see your screen as live WebRTC video. It’s optional and macOS-only, and the sidecar is built from source — so use the one-line source installer instead of the PyPI package (it clones the repo and compiles the sidecar):
curl -fsSL https://raw.githubusercontent.com/pupa-app/pupa-backend/main/install.sh | bashEnable the broker in config.yml (or PUPA_SCREENSHARE=1), then start publishing from the same Mac:
screenshare: true # in ~/.pupa-backend/config.ymlpupa-backend screenshareRequires macOS 14+ and the Xcode toolchain (the first run compiles the Swift sidecar). The broker stays off by default and on hosted multi-tenant deployments.
Deploy to the cloud (Railway)
Don’t want to keep a laptop running? The repo ships a ready-to-deploy Railway config — railway.json and a Dockerfile at the root, plus a baked cloud-config.yml with multi-tenant-safe defaults. You just fork it, point Railway at your fork, add a database, and set a few secrets:
- Fork pupa-backend on GitHub.
- On Railway: New Project ▸ Deploy from GitHub repo → pick your fork. Railway auto-detects
railway.json+Dockerfileand builds the image. The first deploy fails until env vars are set — that’s expected. - Add the Postgres plugin (New ▸ Database ▸ PostgreSQL). Railway injects the connection string, which the backend auto-parses into its checkpointer + store.
- Set the service Variables, then redeploy:
ANTHROPIC_API_KEY=sk-ant-... # or OPENROUTER_API_KEY=sk-or-...
PUPA_API_KEY=<random> # openssl rand -hex 32 — bootstrap pairing key
DATABASE_URL=${{Postgres.DATABASE_URL}} # reference the Postgres pluginThe safety posture (shell off, screen-share off, no MCP servers) is baked into the image — don’t override it on a public deployment. Railway redeploys on every push, so your fork stays in sync. Once the deploy is green, pair against your https://<svc>.up.railway.app URL.
Optional: add OPENROUTER_API_KEY, AWS_*, or LANGFUSE_* to unlock more models and tracing. Full walkthrough in docs/deploy.md.