Skip to Content

CLI

The appfunnel CLI pulls a funnel down as local files, runs it against a real dev server, and pushes and publishes your changes — so you can author in your own editor with your own tooling instead of the in-browser editor. It works on v2 (React) funnels.

Install and authenticate

npm install -g appfunnel appfunnel login

login runs a device flow: it opens https://appfunnel.net/cli/authorize in your browser (“Opening your browser to authorize…”), you click Authorize on the page, and the CLI catches the callback on a local 127.0.0.1 port. The browser page confirms “Logged in ✓ — You can close this tab and return to the terminal.” and the terminal prints ✓ Logged in as <email>.

If a browser can’t open, there’s a paste fallback running simultaneously — the terminal shows “Waiting for browser… or paste an API token here and press Enter:”, so you can paste a token instead. The login mints a 7-day token, stored in ~/.appfunnelrc.

Tokens last 7 days — you’ll re-run appfunnel login about weekly. The device flow (or its paste fallback) is the only way to mint a token; there’s no API-tokens page in the dashboard to create one from.

Two more auth commands:

  • appfunnel whoami — prints ✓ Logged in as <email> plus your user id. An expired token reports “Token is no longer valid.” with the hint to run appfunnel login again.
  • appfunnel logout — clears the stored token (✓ Logged out.).

Pull

appfunnel pull # interactive project + funnel pickers appfunnel pull <funnelId> # a specific funnel

pull downloads the funnel’s current draft into the working directory (“Downloading draft…”), fetching binary assets from the CDN with a sha256 manifest. On success:

✓ Pulled <name> — <n> files, <n> assets Edit locally, then appfunnel push to save your draft.

Pull into an empty folder. It writes a .appfunnel/ directory (link + asset manifests) — “the CLI’s equivalent of .git/ — that binds the folder to that funnel so later commands know what they’re operating on. If a funnel or draft isn’t found (404) it hints to check the id and that it’s a v2 React funnel; a v1 funnel has no draft to pull.

Dev

appfunnel dev # http://localhost:5173 appfunnel dev -p 3000 # custom port

dev runs a local dev server with hot reload against your files, rendering the funnel in test mode so you see changes as you save. It auto-installs dependencies (bun/pnpm/yarn/npm, whichever it detects) on first run.

appfunnel dev renders from your local files only — it does NOT change your draft. Edits hot-reload in the dev server; nothing is saved to the server until you push. The success line says exactly this: “Edits hot-reload here. This does NOT change your draft.”

If the folder has no funnel.ts, dev refuses (“No funnel.ts in this directory.”) and points you to pull one first.

Push

appfunnel push appfunnel push --force # skip the staleness prompt

push uploads changed files (and changed binaries) and saves them as your server draft. It ignores .appfunnel, node_modules, .git, dist, .turbo, .next, .vite and skips files over 6MB.

Before overwriting, it runs a soft staleness guard. If the server draft is newer than your last pull — someone may have edited the funnel in the web builder — it warns and asks:

! The server draft is newer than your last pull (server <ts> vs local <ts>). Someone may have edited this funnel in the web builder. Pushing overwrites it. Push anyway? [y/N]

The default is No (and in a non-interactive shell it’s automatically No). Decline and it tells you to appfunnel pull first. On success: ✓ Pushed <n> files… to <name> with a nudge to publish.

Publish

appfunnel publish appfunnel publish --no-push # publish the current draft without pushing first

publish pushes first (a cancelled staleness prompt aborts the publish), then triggers a deployment and polls it every 1.5 seconds, showing a spinner through the pipeline stages — “Publishing… SOURCE”BUILDUPLOADPROMOTE — up to a 5-minute cap. Success: ✓ Published <name> live ✓.

If a deployment is already running you get a 409 (“A deployment is already running for this funnel.”); a build failure surfaces the deployment error and which stage it failed at; a poll timeout points you to track it in the dashboard.

Status

appfunnel status

Prints the account, funnel, project, API, and last-pulled time, plus a sync line:

  • “up to date with the server draft”
  • “server draft is newer — someone edited in the web builder; pull to get it”
  • “could not reach the server”

A typical loop

Pull the funnel

appfunnel pull into an empty folder and pick the funnel.

Iterate locally

appfunnel dev and edit funnel.ts and your pages with hot reload.

Save the draft

appfunnel push to save your changes as the server draft.

Go live

appfunnel publish and watch it through to ✓ Published.

CLI vs editor vs connected agent

You have three ways to change a funnel, and they operate on the same draft. Use the web editor (overview) when you want the visual preview, the AI assistant, and the config views (offerings, localization, experiments) in one place — it’s the default. Use the CLI when you’d rather author in your own editor with your own tooling and a real local dev server, and drive push/publish from the terminal. Use a connected agent (connected agents) when you want a coding agent (Claude Code, Cursor, Codex) to edit the funnel through the live editor session — the agent gets the editor’s tools while you keep the preview open, and publishing stays a human action in all three.

Last updated on