Skip to content

CLI reference

The CLI installs two identical binaries: blogwright and the short alias bw. Every invocation follows the same shape:

Terminal window
blogwright <command> [env] [options]

The environment defaults to production. Configuration is loaded from config/<env>.jsonc at the repo root, falling back to ops.config.jsonc - see the configuration reference for every key, and environments for how envs map to AWS resources.

Most commands take the environment as an optional first positional. Three commands take a build hash first, and plugin actions consume their declared words before the environment:

Commands Layout
bootstrap, deploy, stage, delete, destroy, history, status blogwright <command> [env]
rollback, logs, verify, promote blogwright <command> <hash> [env]
preview deploy, preview destroy blogwright preview <action> <id>
<plugin> <action> (including multi-word actions) blogwright <plugin> <action> [env]
plugin add, plugin remove blogwright plugin <action> <name> [env] (env selects removal teardown only)
pds keygen, pds login, pds init, pds sync blogwright pds <action> [env]
pds secret status, pds secret delete blogwright pds secret <action> [env]

Environment resolution is --env flag, then the positional, then production. The same precedence applies to --id over the preview id positional. A hash command given --hash takes its only positional as the environment (blogwright verify --hash 4f3a2b1c staging); passing a hash both ways (--hash and two positionals) is refused, as are a missing hash and one that is not letters and digits (a hash names S3 keys, so /, .. and spaces are never accepted) - before any config is loaded or AWS is called. Preview commands always run in the fixed preview environment - they take no env at all.

Unrecognized flags are an error. --help prints usage and exits 0; running with no command prints usage and exits 1.

Terminal window
blogwright init

First-run wizard. Asks for the site name (a lowercase slug that names every AWS resource), AWS region (default us-east-1), optional custom domain and optional GitHub repo for CI deploys, then whether the app is server-rendered. For a server-rendered app it asks the framework (astro, sveltekit, nuxt, tanstack-start, nextjs or other) and writes its ssr block, with the entry shim settings for a known framework. When your shared AWS files define profiles, it asks which one this environment uses (defaulting to --profile, then AWS_PROFILE, when the files define it; answer none for default credentials). It then offers to detect the AWS account the config’s credentials belong to - the chosen profile, or with none your exported or default credentials, never an unsaved AWS_PROFILE - and pin it as aws.accountId, warning when AWS_PROFILE or exported credentials in the shell would override the config - signing in through aws sso login or aws login first if the credentials have expired. A failed detection only warns; init carries on and leaves the account unpinned. Detection is not offered while AWS_ENDPOINT_URL is exported. It also asks each installed plugin’s questions, then writes a commented config/production.jsonc at the repo root and prints the next steps.

init runs before any config or AWS context exists, so it takes no environment and only --profile. It refuses to run (exit 1) when:

  • the terminal is not interactive - in CI or plain mode, write the config by hand instead; only region and siteName are required
  • config/production.jsonc already exists - edit it directly

Each question allows three attempts before the wizard gives up. Discovered plugins with an init contributor can ask additional questions and contribute blocks to the same file. PDS declares publication setup as its own init action and contributes no first-run config questions.

Terminal window
blogwright bootstrap [env] [--switch-mode] [--prune-aliases]

Creates or reconciles the full infrastructure graph for the environment - safe to re-run, including after a failure partway: state is persisted even when a step fails, and resources a crashed run created but never recorded (such as a CloudFront distribution hitting a duplicate-alias conflict) are adopted rather than duplicated.

--prune-aliases detaches CloudFront aliases that are attached to the distribution but not named by domain and aliases, and requests a certificate for the narrower set. Without it they are reported and left attached; bootstrap never removes a live hostname as a side effect. See multiple domains.

--switch-mode allows a bootstrapped stack to move between static and SSR serving. Without it, bootstrap refuses when the configured mode (presence of an ssr block) differs from the mode the live distribution was left in (a stack bootstrapped before SSR existed counts as static), because the distribution’s default behavior moves before a deploy in the new mode exists: a static → SSR switch answers 503 from the placeholder until the first SSR deploy completes, and an SSR → static switch serves whatever site/ holds until the next static deploy. The refusal names that window and applies nothing; with the flag the same window is repeated as a warning and the distribution converges in place. An environment with an ssr block also enables S3 versioning on its bucket here (and never suspends it again). When a domain is configured (via --domain or the config), bootstrap requests the ACM certificate and prints the DNS validation CNAMEs, and the distribution gets the domain as an alias. On completion it prints the CloudFront domain the site is served from. See deploying and custom domains.

After successful site reconciliation, bootstrap reads scoped state keys in the site bucket and warns once per plugin to run blogwright <plugin> bootstrap <env>. It checks the current environment, including uninstalled plugins; no key means no warning. A failed listing produces a warning without failing a successful bootstrap. The site command performs no plugin discovery and reconciles no plugin resources.

Terminal window
blogwright deploy [env] [--refresh] [--force-unlock]

Zips the repo (honoring .gitignore plus the sourceIgnore/sourceInclude config), names the zip by the jj/git revision hash, and uploads it to build/<hash>.zip. If the build-agent bundle changed, the builder MicroVM image is rebuilt first (a no-op otherwise). The MicroVM pulls the zip, runs pnpm install && pnpm build, and syncs the output to the live site/ prefix; only files whose content changed are re-uploaded, and only the changed CloudFront paths are invalidated (past the configured invalidationMaxPaths cap, everything - /* - is invalidated instead). On a TTY the command ends with a summary card: revision, environment, source size, build duration, invalidation, and the site URL.

A static deploy also records the build’s artifact manifest at build/artifacts/<hash>/<buildId>.json - every published path, byte count and SHA-256, an aggregate release hash and a release identity - prints the release, and keeps the first manifest recorded for the revision, write-once, at build/artifacts/<hash>.json. It warns when the builder could not record one (a stack that needs bootstrap to grant build/artifacts/*/*), and when a redeploy of the same hash is not byte-identical to the original.

After every successful production deploy, standard.site records are re-synced to the PDS when configured - non-fatally, so a PDS outage never fails a good deploy (see pds sync).

--refresh re-uploads every file even if unchanged, so metadata fixes (content types, object tags) reach live objects the ETag comparison would otherwise skip. Pass it once after upgrading blogwright across a metadata change. A change to the cacheControl rules needs no flag: the deploy refreshes on its own, as an implicit --refresh (see Caching).

With an ssr block in the config, the build’s output is packaged and published as a Lambda Web Function revision instead of synced to site/: the deploy preflights the endpoint, creates a revision only when the artifact or a revision-scoped setting changed, probes it until it serves, restores the previous revision if it never does, and invalidates /*. See Deploying.

deploy refuses, before anything is built, when the configured serving mode (static or SSR) is not the one the stack’s distribution serves - the state between adding or removing the ssr block and running bootstrap --switch-mode. rollback refuses in the same state.

deploy, rollback, stage, promote and delete each hold the environment lock (build/lock.json) while they run, so only one of them changes an environment at a time. Another one started meanwhile refuses, changing nothing, and names the holder - its command, hash, AWS identity and start time. --force-unlock replaces a lock left by a command that is no longer running. See the environment lock.

Terminal window
blogwright stage [env] [--refresh] [--force-unlock]

Builds the working copy exactly as deploy does - same source zip, builder MicroVM, and robots.txt/sitemap.xml for this environment - but publishes the output to releases/<hash>/ instead of site/. Nothing serves that prefix and nothing is invalidated. The build records its own artifact manifest; stage verifies every staged object against it and then records it as the staged release, build/releases/<hash>.json, which promote checks against. When the revision has no original artifact manifest yet, the staged one becomes it; when it has one from a build with other bytes, the original is kept and stage warns that verify <hash> will check it. The summary prints the release identity, the release hash, and the promote command to run next. The build appears in history as staged, in a row of its own: a stage never changes the record of a deploy of the same hash.

--refresh re-uploads every file into the staged prefix, even content an earlier stage of the hash already wrote, so a metadata fix reaches the staged copy.

Static environments only: refuses in an SSR environment and on the preview stack. Refuses before building on a stack bootstrapped before staging existed - run bootstrap once to grant the builder releases/*. See Release gating.

Terminal window
blogwright promote <hash> [env] [--refresh] [--force-unlock]

Publishes a staged release to the live site without rebuilding. It refuses a hash with no staged release record, then verifies the staged objects against that record and refuses - copying nothing - if any differs. It converges the response headers policy as deploy does, then server-side copies every file whose live ETag differs from the staged one into site/ (bytes, content type, Cache-Control and tags as staged), deletes live objects the release does not contain once every copy has succeeded, invalidates the copied and deleted paths under the same rules as a deploy (/* past invalidationMaxPaths), and finally verifies site/ against the staged release record, so it succeeds only when the live bytes equal the staged bytes. The summary names the release it replaced and the one it published; history shows the staged build as promoted.

The copies run in parallel, so while they run some pages may already be new and others still old. If the command stops partway, the live-release record says so rather than naming the release it was replacing; run it again to finish.

--refresh copies every staged file instead of only the changed ones. Without it, a file whose content is unchanged keeps the content type and tags it already had in site/, so pass it after a content-type fix or once tags can be copied. Each copy keeps the staged object’s Cache-Control; when the staged release’s cacheControl rules are not known to match the ones site/ was written with, every file is copied so none keeps an older header, and site/’s Cache-Control record takes the staged rules (with a warning when the config’s rules have changed since staging). Every path then counts as changed for invalidation; nothing is invalidated only when nothing was copied or deleted.

Post-deploy plugin steps, such as the standard.site sync, run only when the working copy is <hash>; otherwise promote warns and skips them. When the caller may not copy object tags (s3:GetObjectTagging on releases/* and s3:PutObjectTagging on site/*, granted by bootstrap), it promotes untagged and warns once, naming the promote --refresh that copies them later. Any other denial fails the promotion.

A staged copy that no longer matches its record is refused with the missing, changed and unexpected counts and the likely cause; stage the hash again. Like a republishing rollback, promote warns when the hash was staged again since it was last published, and when the staged robots.txt or sitemap.xml was written for another origin than the environment has now.

Terminal window
blogwright rollback <hash> [env] [--refresh] [--rebuild] [--force-unlock]

Restores an earlier release. Find hashes with history. In a static environment, rollback chooses one of three outcomes, and says which as it starts:

  1. Republish. The hash has a staged release record (build/releases/<hash>.json, written by stage). The rollback republishes the bytes stored under releases/<hash>/, with no rebuild. It goes through the same code as promote:

    • it verifies the staged objects against the record, then converges the response headers policy;
    • it copies the changed files into site/, deletes stale objects and invalidates those paths;
    • it verifies site/ against the record.

    It then prints the release that was live (previous release) and the one it restored (restored release), each with its release hash. history shows the staged build as promoted, at the rollback’s time.

  2. Refuse. The hash has a staged release record, but the record is unreadable, or the staged copy no longer matches it. Either an S3 lifecycle rule expired some or all of its objects, or the copy was changed after staging. The rollback refuses before anything is published, because a quiet rebuild would publish bytes no review covered. The error gives the missing, changed and unexpected counts and the likely cause, and names both ways out:

    • stage the hash again from a checkout of it, then roll back again;
    • or run rollback <hash> --rebuild.
  3. Rebuild. The hash has no staged release record, or --rebuild was passed. The rollback checks that the source zip build/<hash>.zip exists (errors if it does not), re-runs the builder against it and invalidates the changed paths. It then prints the release hash of the hash’s original artifact manifest and the rebuild’s own. It warns when the rebuild is not byte-identical, when the rebuild recorded no manifest, or when the original could not be read; in each of the last two cases the rebuild cannot be checked. The original is never replaced by the rebuild.

Rolling back to the live release republishes it too. That re-checks the staged copy and site/, and repairs any difference. The command says the release is already live, and when nothing differed it reports that nothing was copied, deleted or invalidated.

Warnings before a republish:

  • Staged again. The hash was staged again since its last promote or rollback, and the staged bytes now differ from the ones published then. The command names both releases. Every publication is kept in build/published/<hash>.json.
  • Another origin or seo config. The staged robots.txt or sitemap.xml was written for another origin or seo config than the environment has now, for example after a domain change. The staged bytes are published as they are; stage the hash again to publish them for the current origin.

--refresh re-uploads (when republishing: copies) every file. --rebuild is refused in an SSR environment.

In SSR mode, rollback instead reproduces the revision that hash was deployed with, from the artifact version and the webFunction settings recorded in its manifest - without a rebuild - and skips creation when that revision’s fingerprint is already what serves. It falls back to re-running the build when the manifest has no webFunction block, the recorded artifact version is gone, or ssr.staticDir is configured (the artifact carries only the server, and the fingerprinted assets its pages reference were removed from site/ by later deploys). Both paths run the same endpoint preflight, readiness probe and /* invalidation a deploy does.

Post-deploy plugin steps run on the same rule as promote: only when the working copy is <hash>. The standard.site sync is one such step. They publish the working copy’s content, which matches the live site only then; otherwise the rollback warns and skips them. To realign plugin content, check out the rolled-back revision and run the plugin’s command, for example blogwright pds sync.

Terminal window
blogwright delete [env] [--force-unlock]

Empties the live site/ prefix and invalidates CloudFront. Infrastructure, build artifacts, and deployment history stay intact - a later deploy or rollback restores the site. Note it takes effect immediately, without a confirmation prompt. Refuses in SSR mode: emptying site/ cannot unpublish a Web Function revision, whose routes would keep answering from a server whose static assets are gone. Switch the stack back to static serving and deploy, or destroy it.

Terminal window
blogwright destroy [env] --yes [--force-unlock]

Tears down the site infrastructure for the environment and deletes its state. Refuses to run without --yes, and while another command holds the environment lock (--force-unlock overrides). Running builder MicroVMs pin the builder image, so destroy lists them and asks to terminate them first (default yes; on a non-interactive terminal the default applies) - answer no to cancel and let in-flight builds finish.

Before teardown mutations, any state/<env>.<plugin>.json in the site bucket blocks destruction, even an empty-state object or an uninstalled plugin’s record. The refusal names blogwright <plugin> destroy <env> --yes. Reinstall an uninstalled plugin to regain that command. --yes does not bypass this guard; access/listing failures abort, while a confirmed missing bucket permits interrupted cleanup.

Terminal window
blogwright history [env]

Lists deployment history from the stored build manifests (build/manifests/) and staged build records (build/staged/), newest first. A staged build shows status staged until it is promoted, then promoted, ordered by its promotion time, or failed when its build failed; a revision both deployed and staged has a row for each. The live marker goes to the newest successful deploy or rollback, or promotion. On a TTY it renders a table; piped or --plain output is stable columns - hash, status, time, duration - for CI logs and agents. time is when the row last changed the site: the build’s finish, or a promoted stage’s promotion. Once any manifest records a Web Function revision (SSR mode), both forms append a revision column: the revision id a hash was verified with, or - for a hash with none. A hash with a revision is one rollback can reproduce without a rebuild. A corrupt manifest is skipped with a warning rather than failing the listing.

Terminal window
blogwright logs <hash> [env]

Shows the CloudWatch build logs for a hash’s newest build - its deploy, or a stage made after it - filtered to that build’s time window (plus a minute either side) from its record. If the manifest is unreadable, the command warns and shows the unfiltered log window. Each line is prefixed with its ISO timestamp.

Terminal window
blogwright status [env]

Shows the planned infrastructure graph against live state: each resource node is read from AWS and reported as present or missing, with its recorded outputs. On a TTY this renders as a drift tree; plain output is one stable line per resource. Read failures are reported per node without aborting the rest. See operations.

Terminal window
blogwright verify <hash> [env] [--staged]

With --staged, checks the copy stage stored under releases/<hash>/ against the staged release record (build/releases/<hash>.json) instead of the live site against the original - the check to run between staging and promoting. After a promotion of a staged build whose bytes differ from the revision’s original, verify <hash> reports those differences; promote has already checked site/ against the staged release.

Checks the live site/ prefix byte for byte against the original artifact manifest recorded for <hash> (build/artifacts/<hash>.json). Each object under site/ is classified from the bucket listing first:

  • An object the manifest lists, with the recorded size, is read and its SHA-256 compared.
  • An object the manifest lists with a different size is reported as changed without being read - its size already differs.
  • An object the manifest does not list is reported as unexpected without being read, because CloudFront would serve it.
  • An empty folder marker (a zero-byte key ending in /, as the S3 console creates) is never served, so it is listed as ignored and not counted.

Each difference prints as one stable line - missing <path>, changed <path>: expected … found …, or unexpected <path> - and any difference exits 1 with a N missing, N changed, N unexpected summary. A match prints the location, release identity, file count and release hash, and exits 0. On a TTY a status line counts the objects as they are checked.

Reads run eight objects at a time, and an object larger than 8 MiB is read in 8 MiB ranges, so memory stays bounded however large the site’s files are. Every read of an object is conditional on the ETag the listing reported: an object deleted while verify runs is reported as missing, and one replaced while it runs fails the command with its key, so run verify once nothing is deploying. Any other read failure fails the command naming the object and the release.

It refuses a hash with no original artifact manifest (a build from before artifact manifests, or an SSR deploy), refuses in an SSR environment, where a revision pins its artifact object version instead, and refuses the preview stack, whose PR previews record no artifact manifest.

Terminal window
blogwright whoami [env]

Shows which AWS credentials this environment’s commands would use, whose they are, and whether the account pin accepts them. It resolves the profile exactly as every other command does and calls sts:GetCallerIdentity, but loads no state. It works before a repo is configured: with no config file, or outside a repository, it signs STS in us-east-1 and reports no pin. An explicit --config that does not exist is still an error.

account 123456789012
identity AdminRole
arn arn:aws:sts::123456789012:assumed-role/AdminRole/ant
profile mysite-prod (config)
env production (config/production.jsonc)
region eu-west-2
pin matches aws.accountId

profile shows none (environment credentials) or none (default credentials) when no profile is named; pin is matches aws.accountId, not set, or skipped (endpoint override). Exits 0 when the identity resolves and the pin matches or is absent, and 1 for a missing profile, unusable credentials, or a pin mismatch. Accepts --env, --config, --profile and --endpoint.

The preview group manages the shared PR-preview stack. All preview commands run in the fixed preview environment - there is no env positional or --env. See PR previews for the architecture and CI wiring.

Terminal window
blogwright preview bootstrap --domain preview.example.com

Provisions the shared preview stack - one CloudFront distribution with a host-routing function, plus the GitHub OIDC deploy role. A domain is required (flag or config); it must be a Route53 hosted zone so the wildcard certificate and DNS records can be created automatically.

Terminal window
blogwright preview deploy <id> [--refresh]

Builds the current repo in a MicroVM and publishes it to previews/<id>/site/, then prints the preview URL https://<id>.<domain>. The id (positional or --id) must be lowercase alphanumeric with dashes, e.g. pr-42. Preview objects carry the id in their environment tag (preview-pr-42) so per-PR cost and cleanup queries work.

Terminal window
blogwright preview destroy <id>

Removes one preview by deleting its previews/<id>/ prefix and reports the object count. No cache invalidation is needed - the preview distribution does not cache.

Terminal window
blogwright preview list

Lists the active preview ids with their URLs, or no active previews.

Terminal window
blogwright preview teardown --yes

Tears down the whole shared preview stack and deletes its state. Refuses without --yes; like destroy, it refuses while preview-scoped plugin state exists before clearing running builder MicroVMs. The remedy explicitly selects preview, e.g. blogwright analytics destroy preview --yes.

Terminal window
blogwright plugin add <name>
blogwright plugin list
blogwright plugin remove <name> [env] [--yes]
blogwright <plugin> init [env]
blogwright <plugin> bootstrap [env]
blogwright <plugin> status [env]
blogwright <plugin> destroy [env] --yes

plugin add analytics installs blogwright-analytics at the running CLI’s exact version. Already-prefixed and scoped npm package names are accepted literally; version suffixes, URLs and filesystem paths are rejected. An existing direct manifest dependency is left untouched, so this command does not upgrade plugins. The exact pin is made at install time, not maintained when the CLI is upgraded. Current discovery scans only names starting with blogwright-; accepting a scoped package name for installation does not make it discoverable.

plugin list reports namespace, package, version and owned config key, plus named load failures. Both first-party plugins, PDS included, are discovered only once the consumer installs them. Listing returns 0 even when it reports a failed plugin; unknown namespaces/actions return 1 with available-action or plugin-list guidance. Add/list/remove run without site config or AWS credentials, except when an operator chooses resource teardown during removal.

Removal requires a direct dependency in the consumer manifest. For a loadable plugin with nodes, an interactive session asks whether to destroy its resources for the selected environment, with No as default. Yes destroys first and stops removal if destruction fails; No uninstalls and preserves resources and config. plugin remove --yes means uninstall while keeping resources. A noninteractive session without that flag refuses when it cannot ask about a node plugin. For scripted teardown run <plugin> destroy <env> --yes first, then plugin remove <name> --yes. Configuration remains for later reuse. A broken package can be removed without running its unavailable lifecycle; reinstall it when resources still need teardown.

Generic init asks the plugin’s contributor and textually inserts its block into existing JSONC, preserving comments and formatting. Existing keys and non-object files are refused. A declined contributor writes nothing. A declared init command owns its own behavior: pds init creates the publication, not a config block.

Plugins with nodes get their own bootstrap/status/destroy graph and state/<env>.<plugin>.json state. Site lifecycle commands never reconcile those nodes. Destroy requires --yes; a declared status command can provide more than node presence. Built-in bootstrap/destroy action names are reserved from plugin command declarations. The SPI remains internal and unversioned.

The optional analytics plugin adds analytics init, bootstrap, status, dashboard, backfill, and destroy. Its dashboard listens on loopback port 4317 by default; backfill imports whole days strictly before first delivery and omits the boundary day. See Analytics for setup, the dashboard, retention and privacy, and Plugins overview for the plugin model.

The pds group manages standard.site publishing over AT Protocol. All of it requires a pds section in the config; keygen, login, init, and sync also require a configured domain. Run the setup commands in order - keygen, commit and release, login, init, sync.

Terminal window
blogwright pds bootstrap [env]
blogwright pds status [env]
blogwright pds destroy [env] --yes

PDS owns a separate IAM inline policy. Run pds bootstrap <env> once per GitHub OIDC stack to attach blogwright-pds to the site’s deploy role before relying on production post-deploy sync. Site bootstrap must create the role first. The policy grants access only to the configured PDS secret; it is skipped without githubRepo and for the preview environment. Staging is eligible.

The additive migration notice shipped before the site’s old PDS statement was removed. If a stack never installed the plugin grant, post-deploy sync warns until pds bootstrap repairs it. pds destroy removes only the plugin policy and scoped state, leaving the site role and secret; pds secret delete controls the latter. Site teardown refuses while the PDS scoped key remains.

Core no longer validates the PDS block on unrelated built-in commands. PDS actions still validate it. The raw validator preserves optional secretName; consumers resolve <siteName>/atproto using the shared PDS resolver. Existing config files need no migration, and the six publishing actions retain their names and arguments.

Terminal window
blogwright pds keygen [env]

Generates the OAuth confidential-client key (kid <siteName>-oauth-<date>): the private JWK goes into the Secrets Manager secret - clearing any existing session, since client auth is bound to the key - and the public half is written into two committed documents the site serves, <publicDir>/oauth/client-metadata.json and <publicDir>/oauth/jwks.json. Commit and release those before running pds login.

Terminal window
blogwright pds login [env] --identifier <handle-or-did>

Interactive OAuth bootstrap. --identifier is required. The command verifies the deployed client documents match the local ones, prints an authorize URL to approve in a browser, then expects the resulting /oauth/callback redirect URL pasted back. The session is stored in the Secrets Manager secret and refreshed automatically on every sync. Logging in with a bare DID skips handle resolution; pds.handleResolver in the config overrides the resolver used for handles.

Terminal window
blogwright pds secret status [env]

Shows the secret’s metadata - name, ARN, last-changed time - and which parts exist: client key (with its kid), DID, and session. It never prints values. If the secret does not exist, it points you at pds keygen.

Terminal window
blogwright pds secret delete --yes [env]

Deletes the secret - logging out and discarding the client key. Refuses without --yes. Deletion is immediate, with no recovery window.

Terminal window
blogwright pds init [env]

One-time, idempotent publication setup: verifies the OAuth client documents are live on the site, then creates the site.standard.publication record - or updates it when the committed well-known file already names one - and writes the two files the site needs, the well-known publication file under the public dir and the atproto JSON data file. Commit both; they verify the publication and drive the post link tags. If the committed well-known points at a publication owned by a different account (a fork, or an account migration), init refuses rather than silently breaking verification.

Terminal window
blogwright pds sync

Reconciles the site.standard.document records on the PDS with the local content collection and reports a summary: documents created, updated, and unchanged. Records whose local post has been deleted are warned about, never deleted. Sync publishes canonical production URLs, so it refuses to run for any environment other than production. It also runs automatically - non-fatally - after every successful production deploy. Record keys are derived from each post’s URL path; see the rkey reference.

Option Applies to Effect
--env <name> env-scoped commands Select the environment (default production); wins over the positional. Ignored by preview commands, which always run in the preview environment.
--domain <fqdn> all but init Custom domain - ACM certificate and CloudFront alias. Overrides domain in the config. Required (flag or config) for preview bootstrap, pds keygen, pds login, pds init, and pds sync.
--config <path> all but init Explicit JSONC config file. When set it is the only file tried - the config/<env>.jsonc → ops.config.jsonc fallback chain is skipped.
--endpoint <url> all but init AWS endpoint override, e.g. http://localhost:4566 for the floci emulator. Under an override the account pin is skipped and a configured profile missing from your AWS files is ignored.
--profile <name> all AWS profile from your shared AWS files (~/.aws/config, ~/.aws/credentials). Beats AWS_PROFILE, exported credentials and the config’s aws.profile; see AWS credentials. init uses it as the default answer to its profile question.
--hash <hash> rollback, logs, verify, promote The build hash, in place of the first positional - which then names the environment.
--id <preview> preview deploy, preview destroy The preview id (e.g. pr-42); wins over the positional.
--identifier <handle-or-did> pds login The account to authorize as. Required.
--plain all Minimal machine-friendly output - no color, no live status, no prompts. Also automatic when output is piped.
--prune-aliases bootstrap Detach attached CloudFront aliases that domain and aliases do not name. Without it they are reported and left attached.
--switch-mode bootstrap Allow a bootstrapped stack to move between static and SSR serving. Without it, bootstrap refuses a mode change.
--refresh deploy, rollback, preview deploy, stage, promote Re-upload every file, even unchanged ones, so metadata fixes (content types, object tags) reach objects the ETag comparison would otherwise skip. For promote, and a rollback that republishes a staged release, copy every staged file rather than only the changed ones.
--force-unlock deploy, rollback, stage, promote, delete, destroy Replace the environment lock a command left behind when it stopped. Use it only once that command is known to be no longer running.
--staged verify Verify the release staged under releases/<hash>/ instead of the live site/.
--rebuild rollback Static environments: rebuild from the archived source even when the hash has a staged release - the way out when its staged copy can no longer be republished.
--yes site/plugin destroy, preview teardown, pds secret delete, plugin remove Required for explicit destruction. For plugin remove it skips the teardown question and keeps resources.
--help all Print usage and exit 0.

Output has two presentations, chosen once at startup:

  • Interactive - when both stdin and stdout are TTYs and --plain is not passed: color, a transient live-status line for build progress, confirmation prompts, the deploy summary card, the history table, and the status drift tree.
  • Plain - with --plain, or automatically when stdin or stdout is not a TTY (piped output, CI): durable line-oriented text with color stripped, no status line, and no prompts - confirmations take their default, and first-run init refuses to run. Plugin removal refuses when it cannot ask about teardown unless --yes explicitly selects keeping resources. The plain forms of history and status are a compatibility contract for CI systems and agents.

NO_COLOR disables color only - interactive rendering is otherwise kept. Progress and results go to stdout; warnings and errors go to stderr.

Every command that loads an environment first prints one identity line naming the AWS account, the identity, the profile and the pin state:

aws 123456789012 · AdminRole · profile mysite-prod (config) · pinned

The last field is pinned, unpinned or pin skipped. Interactive sessions print it with the rest of the output; plain and piped sessions write it - and notices such as ignoring aws.profile ... or account pin skipped - to stderr, so the plain stdout of history, status and whoami is unchanged.

Code Meaning
0 The command completed. Also --help.
1 No command given (usage is printed); unknown plugin namespace, plugin management action, preview action, or plugin action; or any runtime failure - missing required positional, refused destructive operation, missing config, missing AWS profile, unusable credentials, account pin mismatch, AWS error - with the message on stderr.

See troubleshooting for what common failures mean and how to recover.