CLI reference
The flypod CLI deploys a static directory or file to a public URL with one command, then manages versions, rollbacks, and account ownership.
flypod is a zero-auth static-site deploy primitive. One command ships a directory or single file to a public URL. Logged in, deploys are permanent and owned; anonymous, they live 14 days.
Install
npx flypodOr install globally:
npm i -g flypodThe binary is flypod. The first positional argument that names a subcommand is dispatched (a flag's value never counts, so flypod --site list update is update for the site list); otherwise the argument is treated as a deploy path. flypod deploy [path] is the same as flypod [path]. A bare word that is not a path but is close to a verb (flypod verisons) fails with unknown_command and names the verb you probably meant, rather than deploying a folder that does not exist.
An unknown flag is refused on every command, top-level or subcommand, with the code unknown_flag and exit 2 — nothing is silently ignored. A flag that takes a value and is given none fails with usage. --help never runs the command it is attached to: it prints help on stdout and exits 0 (under --json, as {"ok":true,"help":"…"}). Success exits 0; see Errors and exit codes for the rest.
Commands
flypod [path]
Deploy a directory or single file to a public URL; in a folder that already has a site, update it. Also flypod deploy [path].
flypod update
Ship a new version to the linked site; it becomes live.
flypod versions
List a site's versions, newest first, and switch the version timeline on or off.
flypod rollback
Make a previous version live again.
flypod forget
Remove the local folder-to-site memory.
flypod token
Reveal a site's manage token, for acting on it from another machine.
Account commands
login, logout, list, and claim.
flypod comments
Turn comments on or off, and read, post, resolve, and reopen threads.
flypod install skill
Teach your coding agents how to deploy with flypod.
Global flags
| Flag | Description |
|---|---|
--json | Emit one line of machine-readable JSON on stdout, and nothing on stderr. Every command takes it. |
-q, --quiet | Print only the URL on stdout. Read by the deploy and by flypod update. |
-h, --help | Print usage on stdout and exit 0, without running the command. Every command takes it. |
-v, --version | Print the CLI version and exit (top level only). |
Errors and exit codes
Every command fails the same way. In a terminal the reason is on a line starting with ✗, with the next step underneath it. With --json the failure is one line of JSON on stdout and nothing on stderr:
{ "ok": false, "code": "rate_limited", "error": "Too many requests from this network right now.", "fix": "Wait a minute and retry the same command.", "retry_after_s": 30 }Branch on code: it is stable. error is prose and may change. fix, when present, is the literal next step. A failure that came from the server also carries its HTTP status.
The exit code follows code, never the command:
| Exit | Meaning |
|---|---|
0 | Success. |
1 | A runtime error to act on: no build directory, site gone, upload refused. |
2 | Usage: the command line itself needs changing. |
3 | Auth: not signed in, or the credential was refused. |
4 | Retryable: rate limit, server error, network, or a login still pending. |
| Code | Exit | When |
|---|---|---|
usage | 2 | A required argument is missing or malformed: a flag with no value, an invalid --mode, an ambiguous flypod rollback prefix. |
unknown_command | 2 | A bare word close to a verb (flypod verisons), or a flypod comments verb that does not exist. |
unknown_flag | 2 | A flag the command does not take. |
interactive_login_required | 2 | flypod login --json: the browser step is handed back to the caller. See Account commands. |
no_build_dir | 1 | The path does not exist, or no path was given and no build directory was detected. |
no_files | 1 | The directory has no files to upload. |
no_site | 1 | No site is linked to this folder and no --site was passed. On update, versions and rollback the JSON adds reason: never_linked, expired, gone, or other_folder (a folder above or below this one is linked; fix names the --site command). |
no_such_version | 1 | flypod rollback: no version matches, or the live version is already the oldest. |
not_found | 1 | The server returned 404 for something other than a site. |
site_gone | 1 | The site expired or was deleted. |
already_claimed | 1 | The site already belongs to another account. |
invalid_upload | 1 | The server rejected the upload as malformed. |
too_large | 1 | The upload is over the size or file-count cap. Against flypod.dev the CLI checks this before uploading. |
blocked | 1 | Content screening refused the deploy. |
challenge_required | 1 | The server wants a human challenge for this deploy. Signed-in deploys skip it: run flypod login and deploy again. |
bad_request | 1 | Any other 4xx from the server. |
login_expired | 1 | The sign-in code expired before it was confirmed. |
unexpected | 1 | Anything not covered above. |
not_signed_in | 3 | The command needs an account and there is no session. |
unauthorized | 3 | The credential (your session, or a --token manage token) does not grant access to this site. |
token_rejected | 3 | A deploy sent an account credential (FLYPOD_TOKEN or a saved login) and the server did not accept it, or flypod login --token was refused. It never falls back to an anonymous deploy. |
no_token | 3 | flypod token: this machine holds no manage token for the site. |
login_denied | 3 | The sign-in was denied in the browser. |
rate_limited | 4 | Too many requests; retry_after_s says how long to wait when the server gave one. |
server_error | 4 | The server returned a 5xx. flypod update has already retried it once (an update is safe to repeat); when flypod itself answered, the JSON carries a request_id to quote. |
network | 4 | The server could not be reached, or something in between (a proxy, a firewall) refused the request before it got there. |
authorization_pending | 4 | flypod login --device-code: the user has not confirmed within the 25 seconds it waits. The next flypod command finishes the sign-in once they do. |
login_timeout | 4 | flypod login gave up waiting for the browser. |
Environment variables
| Variable | Description |
|---|---|
FLYPOD_TOKEN | Use an account session token for this process (agents / CI). Takes precedence over a saved login. |
DEPLOY_URL | Override the server base URL. Default https://flypod.dev. A saved login keeps the host it was made against, which wins over DEPLOY_URL. |
FLYPOD_CONFIG_DIR | Override the config directory. |
FLYPOD_SOURCE | Override the surface the CLI reports to the server (cli, skill, library, ci, …). By default it is skill when the project has the flypod skill installed, otherwise cli. |
FLYPOD_SHOW_TOKEN | Set (e.g. to 1) to restore the old inline manage_token line in deploy output. Off by default; prefer flypod token. |
NO_COLOR | Disable colored output. |
FORCE_COLOR | Force colored output. |
DO_NOT_TRACK | Set (to anything but 0 or false) to turn off the CLI's random install id, its agent, CI, launch and agent-instructions words, and its failure reports. |
FLYPOD_TELEMETRY | 0, false, off or no turns off the same, for flypod alone. |
The CLI counts deploys per install with a random id, says in one fixed word which coding agent ran it (claude_code, codex, cursor, gemini, opencode, other or none, read from variables those agents set: AI_AGENT, CLAUDECODE, CLAUDE_CODE_ENTRYPOINT, CURSOR_AGENT, GEMINI_CLI, CODEX_SANDBOX, CODEX_SANDBOX_NETWORK_DISABLED, CODEX_THREAD_ID, CODEX_MANAGED_BY_NPM; never the variables' values) and whether CI did (CI, GITHUB_ACTIONS), how it was launched (npx, bunx, pnpm_dlx, yarn_dlx, script, local, global or other, from the shape of its own script path and the package manager's npm_lifecycle_event, npm_command, npm_config_user_agent, npm_execpath), and whether the project's agent instructions name flypod (skill, agents_md, claude_md, cursor_rules, copilot, gemini_md or none: which kind of file, among AGENTS.md, CLAUDE.md, .cursorrules, .cursor/rules/*, .github/copilot-instructions.md and GEMINI.md in the project up to its repository root, has the word flypod in its first 32 KB), and when a command fails against flypod.dev it reports the error code and, for too_large, no_files and no_build_dir, the upload's shape (file count, kilobytes per kind of file, whether the folder has a package.json or a build folder); for no_site, which kind; for unauthorized, which kind of credential was refused, whether this machine knew the site, and whether it was named by the folder's link or --site. Never file names, paths or contents. Reports never print and give up after 1.5 s. The library (deploySite()) sends none of it.
Configuration files
flypod stores config in a per-user directory, never in your project repo. Override the location with FLYPOD_CONFIG_DIR.
| Platform | Default location |
|---|---|
| macOS | ~/Library/Application Support/flypod |
| Windows | %APPDATA%/flypod |
| Linux | $XDG_CONFIG_HOME/flypod (defaults to ~/.config/flypod) |
These files are written with mode 0600:
session.json: the account session created byflypod login.projects.json: the folder-to-site registry. LRU-capped at 64 entries; expired entries auto-prune.pending-login.json: only while aflypod login --jsonwaits for its person. A device code, not a credential, so the next command can finish the sign-in; worthless after ten minutes.install.json: the random install id described above. Not written whenDO_NOT_TRACKis set.
Config files are never placed in your project repo. Nothing flypod writes needs to be committed.
Authentication
Connect the CLI through browser device authorization:
flypod loginFor a stateless agent or CI job, provide an existing account session token:
FLYPOD_TOKEN=<session_token> npx flypod ./distFLYPOD_TOKEN takes precedence over a saved login. With neither, the deploy is anonymous. A credential that is sent and not accepted fails with token_rejected; it never silently becomes an anonymous deploy. See Account commands.