Skip to content

CLI reference

The pageship command signs you in, publishes HTML files as documents, and manages the documents you published. It also installs the Pageship skills for your agents. Commands use the hosted Pageship service unless you pass --server.

Each command prints a short message for a person. Add --json to get one JSON result instead. Agents use JSON.

The CLI needs Node.js 24.11 or newer. Install it globally with your package manager:

npm i -g pageship

Check that your shell finds it:

pageship --version

To update it later, run pageship update.

To run a command without installing the CLI, put npx in front of it, as in npx pageship status.

To have an agent install the CLI and the skills and sign you in, give it the setup guide instead.

Command What it does
login Authorize this installation in a browser and choose your address.
whoami Show who is signed in and this installation’s ID.
status Check whether this installation is ready to publish. Changes nothing.
skills install Install the Pageship skills for your agents.
publish Publish an HTML file as a new document or a new revision.
list List your documents.
history List a document’s revisions.
download Save a document’s HTML to a new file.
visibility Make a document private or public.
viewers List, add or remove the GitHub users who can read a private document.
restore Make an earlier revision current again.
update Update the CLI with the package manager that installed it.

Run pageship on its own, or any command with --help, to see its usage and options.

Authorizes this installation through your browser, then sets your workspace address.

pageship login
Open <link>
Check code <code>. Only approve an installation you started.
Waiting for approval. Press Ctrl+C to stop; you can resume with pageship login --complete.
Authorized as Ada Lovelace. Your credential is stored privately for this server. Workspace address: ada.pageship.dev.

In a terminal, login shows a link and a code, then waits. Open the link on any machine and approve the code with your invited GitHub account. Press Ctrl+C to stop waiting; pageship login --complete resumes later.

Without a terminal, or with --json, login returns at once with authorization_pending, the link in verificationUriComplete, the code in userCode, and next set to login --complete. Approve the code, then run that command.

After you approve, login keeps your existing workspace address, or claims your GitHub login as the address. If that name is reserved or taken, you choose from generated addresses. You cannot type an address of your own.

  • In a terminal, type a number to pick one, r for new choices, or q to cancel.
  • Without a terminal, login returns choice_required with the choices. resolutions holds one complete login --address-choice command for each, plus login --refresh-addresses.

Cancelling or interrupting the choice keeps your credential.

When this installation is already signed in, login reuses the stored credential and only sets the address. Use --restart to sign in again, for example with another account.

Option What it does
--complete Check a waiting authorization once, without waiting.
--restart Drop any waiting authorization and start with a new code, even when signed in.
--address-choice <choiceId> Select one of the generated addresses that login offered.
--refresh-addresses Replace the offered addresses with new ones. Not together with --address-choice.
--server <origin> Use another server.

Login returns:

  • authorized, exit 0: signed in, with the selected address. Only this result means the workspace is ready to publish.
  • authorization_pending, exit 2: approve the code in the browser, then run next.
  • expired, exit 2: the code expired. next starts a new one.
  • choice_required, exit 2: pick an address from resolutions.select, or new choices with resolutions.refresh.
  • reauthentication_required, exit 2: sign in to the dashboard again so Pageship can check your GitHub login, then run next.
  • signed_out, exit 2: the server rejected the new credential. next is login --restart.
  • cancelled, exit 2: you typed q at the address prompt. next offers the choice again.
  • denied, exit 1: the code was denied in the browser.
  • invalid_attempt, exit 1: --complete found no waiting authorization, or the server refused the code for another reason.
  • uninvited, exit 1: the GitHub account is not invited to this server.
  • invalid_action, exit 1: --address-choice and --refresh-addresses were used together.
  • interrupted, exit 130: Ctrl+C. next resumes.

Shows who is signed in, using the stored credential.

pageship whoami
Signed in as Ada Lovelace. Personal workspace: <workspaceId>
CLI installation: <installationId>

The JSON result is admitted, exit 0, with user (id, name), workspace (id), installation (id) for a CLI credential, and the selected address. Without a credential, or when the server rejects it, the result is signed_out, exit 2, with next set to login. An account that is not invited returns uninvited, exit 1.

Option What it does
--server <origin> Use another server.

Each installation has its own credential. The installation ID that whoami prints is not secret. To revoke one, find that ID in the dashboard’s list of CLI installations and select Revoke access. Its next request returns signed_out, exit 2. Other installations stay signed in. Run login on it to authorize it again.

Reports whether this installation is ready to publish, and whether the Pageship skills are installed and current.

pageship status
Compatible with API major 1. Server 0.1.0; supported API majors: 1.
Signed in as Ada Lovelace.
Personal workspace: <workspaceId>
CLI installation: <installationId>
Workspace address: ada.pageship.dev
Publishing setup is complete. Status does not check storage or publish anything.
The Pageship skills match this CLI (0.1.1).

Status checks the service’s API version, then reads the stored credential and your personal workspace. It only reads. It never starts a sign-in, opens a browser, claims an address, uploads anything, or creates or changes a file in the configuration directory. A ready result does not prove that storage works or that a publish will succeed. Each request times out after 10 seconds. It also asks the npm registry for the latest CLI version, for up to 5 seconds.

Option What it does
--server <origin> Use another server.

Status returns:

  • admitted, exit 0: signed in with a selected address, ready to publish. A server too old to report the address leaves it out; next is then an optional login that confirms or chooses one.
  • address_required, exit 2: signed in, but no address is selected. address is null, and next is the login that selects one.
  • signed_out, exit 2: no stored credential, or the server rejected it. next is login, with --complete when an authorization is waiting for approval, or --restart when it expired.
  • uninvited, exit 1: the GitHub account is not invited to this server.
  • unreachable, exit 1: the service did not answer.
  • client_too_old, server_too_old or unsupported, exit 1: the CLI and the server share no API version. The message says which one to update.
  • configuration_unreadable, exit 1: the configuration directory exists but its credentials cannot be read. The message names the directory and how to fix it.

admitted and address_required include user, workspace, installation and address as for whoami. Every result except unreachable also has requiredApiMajor and server, with the server’s serverVersion, currentApiMajor and supportedApiMajors.

Every status result also reports the skills, and these never change the status or the exit code:

  • skills: missing, current, or older than this CLI. When the skills are not current and nothing else needs doing, next is skills install.
  • cli and latest: whether this CLI is current or older than the version npm publishes as latest, and that version. Both are left out when the registry doesn’t answer. An older CLI gets no skills install in next, since it would install its own older skills. Run update, then install the skills.
  • missing: what else the CLI looked for and did not find. It can name the recommended skills frontend-design and design-taste-frontend, and node. The authoring skills’ scripts run with Node 20 or newer from your PATH, which can differ from the Node that runs the CLI.

Installs the Pageship skills that ship inside the CLI: the pageship platform skill, which publishes with this CLI, and the pageship-docs authoring skills, which write documents. See Pageship skills for what each one does.

pageship skills install
Installed 17 Pageship skills for CLI 0.1.1 in /home/ada/.agents/skills.
Linked them from /home/ada/.claude/skills for Claude Code.

It copies each skill to ~/.agents/skills/<name>, which Codex, Cursor, Copilot, Gemini CLI, OpenCode, Amp, Pi, Goose and Windsurf read. When ~/.claude exists, it also links ~/.claude/skills/<name> to that copy for Claude Code. On Windows it copies instead of linking. It installs for your user, not for one project.

The CLI owns the pageship and pageship-* folders there. It records each folder it writes, with its version and a hash of its content, in skills.json in the configuration directory. Running the install again, for example after you update the CLI, replaces those folders and removes the ones this version no longer ships. It leaves other pageship-* folders alone.

Option What it does
--force Replace Pageship skill folders that were changed since, or that this CLI did not install.

It returns:

  • skills_installed, exit 0: lists every folder and link it wrote in folders and links, and the folders it removed in removed.
  • skills_modified, exit 1: a folder it would replace or remove was changed since, or it has no record of writing it. It names them in folders and writes nothing. resolutions.force holds skills install --force.

Both results list in missing what else the CLI looked for, as status does: the recommended skills, in ~/.agents/skills, ~/.claude/skills and Claude Code plugins, and Node 20 or newer. The CLI never installs them, and they never change the result. The setup guide covers them.

To remove the skills, delete the pageship and pageship-* folders in ~/.agents/skills and ~/.claude/skills.

Updates this CLI to the version npm publishes as latest, with the package manager that installed it.

pageship update
Running npm i -g pageship@0.6.0
changed 1 package in 2s
Updated Pageship from 0.5.0 to 0.6.0 with npm i -g pageship@0.6.0.
Run pageship skills install to update the Pageship skills to match.

It tells the package manager from the folder the CLI is installed in, and runs that manager’s global install:

Installed with Runs
npm npm i -g pageship@<version>
pnpm pnpm add -g pageship@<version>
bun bun add -g pageship@<version>
Yarn 1 yarn global add pageship@<version>
Vite+ vp install -g pageship@<version>

It installs the exact version npm publishes as latest. When the registry doesn’t answer, it installs pageship@latest and lets the package manager choose. In a terminal you see the package manager’s output as it runs. With --json, the CLI collects that output and returns it in output only when the update fails.

It returns:

  • cli_updated, exit 0: the package manager installed the new version. manager names it, run is the command it ran, version is the version you had and latest the one installed. When the Pageship skills are installed, next is skills install, since they still match the old version.
  • cli_current, exit 0: this CLI is already the latest version. Nothing ran.
  • install_unknown, exit 1: no supported package manager installed this CLI globally, for example when it is a project dependency or runs through npx. path is the folder it runs from. Nothing ran; update it the way you installed it.
  • update_failed, exit 1: the package manager failed or could not start. exitCode and output hold what it returned, and the message says what went wrong, such as missing permission to write its global folder.

Publishes one HTML file. The first publish creates a private document; later publishes of the same file add revisions to it.

pageship publish report.html
Published privately as a new document: https://ada.pageship.dev/d/Ab12Cd-report

The file must end in .html or .htm, and its path can be relative or absolute. A file over the size limit, 10,000,000 bytes unless the server sets another, is refused before upload. Publishing needs a selected workspace address; without one, publish returns address_required with next set to login.

Option What it does
--new Create a separate private document, even when the file matches an existing one.
--revise <documentId> Publish a new revision of this document. Needs --expected-revision.
--expected-revision <revisionId> The document’s current revision, as you last saw it. A newer revision stops the publish.
--no-marker Publish the file as it is, without adding or changing its document marker.
--idempotency-key <key> Retry an unfinished publish with the key its result returned.
--server <origin> Use another server.

Use --new alone, or --revise together with --expected-revision. The old --address-choice and --refresh-addresses options no longer work on publish; they return invalid_action with the matching login command in next.

A plain publish, without --new or --revise, looks for the file’s document in this order:

  1. The file’s document marker.
  2. The publish manifest, for a file this machine published before.
  3. The file’s repository and path, for a file in a Git checkout.

When none of them matches, it creates a new document. The message says which it did, “as a new document” or “as a new revision”, and the JSON result has change set to created or revised.

A file in a Git checkout with an origin remote belongs to a repository, recorded as its normalized remote without credentials, such as github.com/UzEE/tracksmith. Any other file is a loose file, and a loose file is never matched by its path. The source path is relative to the file’s Git checkout, or else to the current directory, or else, for a file outside the current directory, to the file’s own folder. The repository is fixed when the document is created; the path updates on every publish from a file in a repository. Each revision records the CLI version that published it.

A published file ends with a document marker, an HTML comment that names its document and does not render:

<!-- pageship:v1 document=Ab12Cd -->

For a new document, the CLI proposes the ID, uploads the file with the marker added, and writes the same marker to the local file only after the upload succeeds. The local file and the published revision then hold the same bytes. If the proposed ID is taken, the CLI retries with a fresh one. An existing marker is replaced, never duplicated, and no other byte changes. The CLI skips the write if the file changed during the upload. If it cannot write the file, for example because it is read-only, the publish still succeeds and the message warns that the file has no marker. A UTF-16 file is published without a marker, with a warning.

A marked file revises its document without asking when the manifest links this path to it, or when the file belongs to the document’s repository, for example in another checkout of it. A marked loose file matches a loose document. Otherwise:

  • When the repositories differ, for example in a fork, the result is repository_mismatch, exit 2, with the document and resolutions. Nothing changes.
  • When the marker names a document that does not exist or is not yours, the result is marker_unavailable, exit 2, before any upload. next is the same publish with --new.

--new points the marker at the new document, and --revise at the named one. --no-marker leaves the file untouched, and the follow-up commands it returns keep --no-marker.

After each successful publish, the CLI records the file’s real path, document ID and revision ID in a publish manifest for that server, next to the credentials. A symbolic link resolves to its real path first.

Publishing the same real path again revises that document without asking, and expects the recorded revision. If the document changed since, for example from another machine or the dashboard, the result is revision_conflict, exit 2, with the current document and resolutions. Nothing changes. A retry with an older --idempotency-key leaves a newer entry, and the file’s marker, as they are. For a file without a marker, an entry whose document was deleted or is not yours is ignored, and the next successful publish replaces it. --new and --revise skip the manifest and point the entry at the published document.

When a file in a repository without a marker or manifest entry has the same repository and path as published documents, publish changes nothing and asks which one you meant. In a terminal:

This source path matches published documents:
1. reports/report.html (Ab12Cd) https://ada.pageship.dev/d/Ab12Cd-report
o <number> to open, r <number> to revise, n for a separate document, or q to cancel:

o 1 opens the document in your browser and asks again. r 1 publishes a revision of it, n creates a separate document, and q cancels with cancelled, exit 2. Opening and cancelling change nothing.

Without a terminal, or with --json, the result is source_collision, exit 2, with the matching candidates and resolutions: new, and one revise entry for each candidate. Each entry is a complete command to run.

To revise a document from any file, for example a moved file or a loose file on another machine, name it:

pageship publish report.html --revise Ab12Cd --expected-revision <revisionId>

If a publish fails in transit or you interrupt it, next repeats it safely. Once the upload has started, next carries --idempotency-key with the result’s idempotencyKey; run it with the file unchanged. A key reused with the same input returns the same publication; with different input it returns idempotency_conflict.

  • published, exit 0: document holds its id, slug, title, sourcePath, repository, visibility, current revisionId, byteLength, createdAt and url, and change is created or revised.
  • address_required, source_collision, repository_mismatch, marker_unavailable, revision_conflict, cancelled or signed_out, exit 2: nothing changed. Run next, or the entry in resolutions you choose.
  • invalid_file, upload_too_large, invalid_action, idempotency_conflict, unavailable or document_id_taken, exit 1: fix the cause, then publish again. unavailable means the document named by --revise is not found or not yours.
  • publication_action_unsupported, exit 1: the server is too old for --new or --revise. Nothing was uploaded.
  • request_failed, exit 1, or interrupted, exit 130: next retries safely.

These commands use the stored credential and work only on your own documents. A document that does not exist or is not yours returns unavailable, exit 1. Each result carries the IDs the next command needs.

Lists your documents, 100 at a time.

pageship list
Documents:
Ab12Cd  private  2 viewers  report
  repository github.com/ada/reports
  source reports/report.html
  current revision 5f3c2a1e-8b7d-4c6e-9f0a-1b2c3d4e5f6a
  https://ada.pageship.dev/d/Ab12Cd-report

The JSON result is listed, exit 0, with documents. Each document has its ID, title, repository, source path, visibility, number of viewers (sharedWith), current revision and URL. When more exist, the output ends with the command for the next page, and the JSON result has nextCursor and next.

Option What it does
--cursor <documentId> Continue after the nextCursor of the previous page.
--server <origin> Use another server.

Lists a document’s revisions, newest first, 50 at a time, and marks the current one.

pageship history Ab12Cd
Revisions of Ab12Cd, newest first:
5f3c2a1e-8b7d-4c6e-9f0a-1b2c3d4e5f6a  current  2026-10-19T09:30:00.000Z  18204 bytes
  https://ada.pageship.dev/d/Ab12Cd-report/revisions/5f3c2a1e-8b7d-4c6e-9f0a-1b2c3d4e5f6a

The JSON result is listed, exit 0, with revisions. In JSON, each revision names the cliVersion that published it, or null for a restoration. Paging works as for list. History is visible only to you, even for a public document.

Option What it does
--cursor <revisionId> Continue after the nextCursor of the previous page.
--server <origin> Use another server.

Saves the exact stored HTML of a document’s current revision, or of an earlier one, to a new file.

pageship download Ab12Cd --output report.html
pageship download Ab12Cd --revision <revisionId> --output old.html
Saved 18204 bytes of Ab12Cd to /home/ada/reports/report.html.

Download never replaces a file. When the --output file exists, the result is output_exists, exit 1, and the file is unchanged. A path it cannot write returns invalid_output, exit 1. The JSON result downloaded has the absolute output path, byteLength, and revisionId, which is null for the current revision.

Option What it does
--output <file> Required. The new file to write.
--revision <revisionId> Download this earlier revision instead of the current one.
--server <origin> Use another server.

Makes a document private or public.

pageship visibility Ab12Cd public
pageship visibility Ab12Cd private
Ab12Cd is public. Anyone with the URL can read its current revision: https://ada.pageship.dev/d/Ab12Cd-report

Anyone with the URL can read a public document’s current revision without signing in. A private document is readable only by you and its viewers, after signing in. Making a document public removes all its viewers, and the output says how many; making it private again does not bring them back. Visibility changes nothing else. The JSON result is visibility_updated, exit 0, with the saved document and removedViewers, the number of viewers removed.

Option What it does
--server <origin> Use another server.

Lists the GitHub users who can read a private document, or adds and removes them. A viewer signs in with GitHub to read the document’s current revision. Pageship sends no invitations: send them the URL yourself.

pageship viewers Ab12Cd
pageship viewers Ab12Cd --add octocat --add hubot
pageship viewers Ab12Cd --remove octocat
Found octocat (The Octocat), GitHub ID 583231.
Added octocat (The Octocat), GitHub ID 583231.
Viewers of Ab12Cd:
  octocat (The Octocat), GitHub ID 583231

--add looks the username up on GitHub, prints the account it found and adds that account. Only a personal account can be a viewer. A public document can’t have viewers: make it private first. --remove takes a viewer’s username or GitHub ID and matches it against the document’s viewers. A value of only digits is always a GitHub ID, so remove a viewer whose username is all digits by their GitHub ID. A viewer’s username is the one they had when added or last signed in, so after a GitHub rename two viewers can share one. If a username matches more than one viewer, the result is ambiguous_viewer, exit 1, with the viewers in matches; nothing changes, and you remove one by its GitHub ID.

The command matches every --remove and looks up every --add before it changes anything, then applies the removals and additions together: all of them or none. It takes up to 20 --add and 50 --remove values. Without --add or --remove, the JSON result is listed, exit 0, with viewers. Otherwise it is viewers_updated, exit 0, with added, removed, notViewers for --remove values that matched no viewer, and viewers. If the change fails, nothing changed, and its next repeats the command.

GitHub lookups use the GitHub sign-in Pageship holds for you, not the CLI credential. If GitHub refuses it, the result is github_sign_in_required, exit 2: sign in to the dashboard again with GitHub, then run next.

Option What it does
--add <username> Look up this GitHub username and add the account as a viewer. Repeatable.
--remove <username or ID> Remove the viewer with this GitHub username or GitHub ID. Repeatable.
--server <origin> Use another server.

Copies an earlier revision’s content into a new current revision.

pageship restore Ab12Cd --revision <revisionId> --expected-revision <currentRevisionId>
Restored revision <revisionId> as new current revision <newRevisionId>. Earlier revisions remain in history, and Ab12Cd stays private: https://ada.pageship.dev/d/Ab12Cd-report

Pass the current revision you saw in history as --expected-revision. If the document changed since, the result is revision_conflict, exit 2, with the current document, and next is the history command; nothing is restored. The JSON result is restored, exit 0, with the document and its new current revision. Restoring the current revision returns invalid_restore, exit 1. Visibility, the URL and earlier revisions stay the same.

If a restore fails in transit or you interrupt it, it may still have finished. Run the returned next, which carries --idempotency-key, to get the original result without restoring twice. Reusing that key with other arguments returns idempotency_conflict, exit 1.

Option What it does
--revision <revisionId> Required. The earlier revision to restore.
--expected-revision <revisionId> Required. The current revision you saw. A newer one stops the restore.
--idempotency-key <key> Retry an unfinished restore with the key its result returned.
--server <origin> Use another server.

These work with every command. --json can go before or after the command name.

--json prints one JSON object on one line to standard output, and nothing else: no prompts, no progress and no browser. Help, version and usage errors are JSON too. See JSON results.

pageship --json status
pageship status --json

After the -- separator, --json is an ordinary argument.

--help, or -h, prints usage and options, for the CLI or for one command. --version, or -v, prints the CLI’s version, such as pageship v0.1.1. Both exit 0.

--wizard, --completions and --log-level come from the CLI’s command-line library and work only without --json.

  • --wizard builds a command by asking for each part in turn.
  • --completions <shell> prints a shell completion script for bash, zsh, fish or sh.
  • --log-level <level> sets the minimum level of log messages. Pageship writes none of its own.

Every JSON result has these fields:

Field Meaning
ok true only when outcome is succeeded: the command finished, with nothing left for you to do.
command The command, such as status, skills, help or version. null when none was recognized, or on internal_error.
outcome succeeded, pending or failed.
status The specific result, as listed under each command.
message The text a person would see. Show it; don’t parse it.
next One follow-up command, when there is one.
resolutions Named follow-up commands to choose from, when a decision is waiting.

Other fields depend on the status, such as document, candidates or user. No result contains a credential.

next and each entry in resolutions have a command and an args list. Run one as pageship --json <command> <args...>, with the arguments unchanged. They already carry what the follow-up needs, such as the document ID, the expected revision and the idempotency key, and never contain placeholders.

A successful publish, for example, prints this, shown here across several lines:

{
  "ok": true,
  "command": "publish",
  "outcome": "succeeded",
  "status": "published",
  "message": "Published privately as a new document: https://ada.pageship.dev/d/Ab12Cd-report",
  "change": "created",
  "document": {
    "id": "Ab12Cd",
    "slug": "report",
    "title": "report",
    "sourcePath": "reports/report.html",
    "repository": "github.com/ada/reports",
    "visibility": "private",
    "revisionId": "5f3c2a1e-8b7d-4c6e-9f0a-1b2c3d4e5f6a",
    "byteLength": 18204,
    "createdAt": 1792402200000,
    "url": "https://ada.pageship.dev/d/Ab12Cd-report"
  },
  "idempotencyKey": "0192f3c5-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}

The exit code follows the outcome, in human and JSON modes.

Exit code Outcome
0 succeeded
1 failed: fix the cause first.
2 pending: waiting for your approval or choice. Run next or a resolution.
130 failed with interrupted: Ctrl+C or SIGTERM.

Any command can also fail with usage_error for invalid arguments, request_failed for a network, server or storage failure, or internal_error for a defect in the CLI, each exit 1. A command that contacts the server returns client_too_old, server_too_old or unsupported, exit 1, when the CLI and the server share no API version. A command that needs sign-in returns signed_out, exit 2, with next set to login, or uninvited, exit 1, when the account is not invited to the server.

Add --server <origin> to any command except skills install and update to use another Pageship server instead of the hosted API at https://api.pageship.dev. It applies to that command only. Use it for development, testing or another deployment. No environment variable or file selects a server.

pageship status --server http://localhost:8787
pageship login --server http://localhost:8787

The value is an HTTPS origin with no path, credentials, query or fragment. HTTP is allowed only on localhost, 127.0.0.1 and [::1]. Credentials and waiting authorizations are kept separately for each server, so signing in to one does not sign you in to another. When you pass --server, every follow-up command the CLI returns for that server includes it. A skills install follow-up has no --server.

The CLI keeps its credentials, the publish manifest and the skills record in one private directory:

  • $PAGESHIP_CONFIG_DIR, when set. It must be an absolute path to a private directory.
  • Otherwise $XDG_CONFIG_HOME/pageship, or ~/.config/pageship, on macOS and Linux.
  • Otherwise %LOCALAPPDATA%\pageship on Windows.

Credentials are plain JSON, stored per server origin and never printed. On macOS and Linux the directory is owner-only and the files have mode 0600. On Windows they inherit your user profile’s permissions; the CLI sets no access control lists.