npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@poli-page/cli

v0.8.2

Published

Poli Page CLI — scaffold projects, create templates, and render PDFs from the command line

Readme

Poli Page CLI

npm version CI license

Command-line tool for Poli Page — scaffold projects, manage cloud-hosted templates, render PDFs, and operate document storage from your terminal or your CI pipeline.


Table of contents


Install

npm install -g @poli-page/cli
# or
pnpm add -g @poli-page/cli
# or
bun add -g @poli-page/cli

Requirements: Node.js 22.13 or later.

Verify the install:

poli --version
poli --help

Quickstart

# 1. Authenticate (opens your browser for the device flow)
poli login

# 2. Scaffold a new project
poli init my-invoices
cd my-invoices

# 3. Add a template from the Poli Page starter collection
poli new invoice --from-template showcase/invoice

# 4. Link the project to your organization (writes the cloud manifest)
poli link

# 5. Render a PDF using the local template against the cloud engine
poli render invoice -o invoice.pdf

# 6. Push your work as a SANDBOX version
poli push --message "Initial draft"

# 7. Promote to LIVE when ready
poli versions list
poli promote 1.0.0

To iterate on a template with live feedback against the cloud:

poli watch
# edit your template files; each save syncs to the cloud draft within 2 seconds

Authentication

The CLI supports two authentication modes. Both flow through the same internal resolver.

Interactive (developers)

poli login    # device authorization flow — opens your browser
poli whoami   # show the current session and active org
poli logout   # clear ~/.polipage/credentials.json

After poli login, the session token is stored at ~/.polipage/credentials.json and used automatically by every subsequent command.

Programmatic (CI / CD / scripts)

Set the POLI_PAGE_API_KEY environment variable. The value must start with pp_:

| Prefix | Use case | | ----------- | ----------------------------------------------------------------- | | pp_test_* | Sandbox renders (unmetered, no quota) | | pp_live_* | Live renders (counts in the monthly quota, billable) | | pp_sa_* | Service account — recommended for CI; rotates without breaking dev|

export POLI_PAGE_API_KEY=pp_sa_live_xxx
poli render invoice -o invoice.pdf
poli push --message "Release v1.0.5"

Precedence

When both ~/.polipage/credentials.json (a session) and POLI_PAGE_API_KEY are set, the session wins. poli login does not refuse if the env var is set — it shows an info-message and proceeds, after which the new session takes precedence.

This means a developer can keep POLI_PAGE_API_KEY set in .zshrc for occasional CI-style commands without it interfering with their interactive workflow.


Commands

24 commands grouped by category.

Project lifecycle

poli init <name>

Scaffold a new Poli Page project. Creates the directory, the poli-page.json manifest, templates/, partials/, assets/{fonts,images}/, tailwind.css, and .gitignore.

poli init my-invoices                                   # interactive: prompts to add a starter
poli init . --with-template showcase/invoice            # init in cwd + import an explicit starter
poli init my-app --with-template showcase/quote --template-name custom-quote
poli init my-app --source github:my-org/my-templates --with-template internal/cover

Run without --with-template in an interactive shell to be prompted for a collection, a template (with descriptions), and the destination template name in your project (default = source template name; press Enter to keep, or type a new name). In non-interactive shells (CI), the prompt is skipped and the project is created without a starter — pass --with-template explicitly when needed.

Flags:

  • --with-template <ref> — pre-install a starter template, format <collection>/<template>. Skips the interactive prompt.
  • --template-name <name> — override the imported template's destination name
  • --source <repo> — source repo, format github:<owner>/<repo> (default: github:poli-page/templates)
  • --no-cache — bypass the 24-hour template cache

poli new <name>

Create a new template inside an existing project from a remote starter.

poli new invoice                                # interactive: prompts for collection/template
poli new invoice --from-template showcase/invoice
poli new quote --from-template structures/blank

Run without --from-template in an interactive shell to be prompted for a collection/template. If you decline the prompt (or run in CI without the flag), the command falls back to structures/blank — the minimal blank template — rather than erroring out. Pass --from-template <coll>/<tpl> to pick anything else explicitly.

Flags:

  • --from-template <ref> — collection/template. Skips the interactive prompt.
  • --source <repo> — override the source
  • --no-cache — bypass cache

Authentication commands

poli login

Opens your browser for the device authorization flow and stores the session token at ~/.polipage/credentials.json.

poli logout

Clears the local credentials file.

poli whoami

Displays the active identity and organization. Calls GET /v1/me over hybrid auth (session or API key).

# Inside a linked project (session)
$ poli whoami
[email protected] @ acme (session)

# Anywhere — fallback when no project is linked, lists the orgs the session can see
$ poli whoami
[email protected] (session, 2 organizations: acme, demo-corp)
  Run `poli link` inside a project to bind it to an organization.

# Programmatic (API key)
$ POLI_PAGE_API_KEY=pp_live_xxx poli whoami
pp_live_xxx…abcd @ acme (api-key, environment=live)

Flag: --json to dump the raw /v1/me payload (or, in session-no-org mode, { mode, user, orgs }).

Cloud sync

poli link

Interactively select an organization, create the cloud project, and write the cloud: section into poli-page.json with the four fields the API needs (orgSlug, orgId, projectSlug, projectId). No API key is stored in the manifest — the file is safe to commit.

poli unlink

Removes the cloud: section from poli-page.json. Local files are preserved.

poli watch

Auto-syncs the local project to the cloud draft on every save. Debounce 2s. Each batch sends only the SHA-256 delta via PATCH /api/organizations/:orgId/projects/:projectId/files.

poli watch

Resilience built in:

  • Network errors → exponential backoff (1s, 2s, 4s, 8s, 16s, 30s capped, reset on success)
  • 503 ORGANIZATION_MIGRATING → 5-second retry
  • 403 SYSTEM_PROJECT_LOCKED (the getting-started system project) → friendly error + exit
  • Other API errors → logged, watch continues, next save retries

Requires a TTY. Refused with exit 2 when run in a non-interactive context.

poli checkout <version>

Restore a specific published version locally. Destructive — overwrites any local file with the same path. Always commit your work before running.

poli checkout 1.0.5    # exact semver only

latest and partial semver (1.0, 1) are rejected with a friendly hint.

Side effect: writes cloud.track = "X.Y" to the manifest (derived from the checked-out version). poli push --patch / --minor will then be anchored on that track — see the hotfix flow above.

Versioning

poli push

Sync the current local draft and create a new SANDBOX version. The body shape is one of two mutually exclusive forms (api-spec §9.1):

  • Bump-driven: --patch (default), --minor, or --major. Anchored on the manifest's cloud.track (set by poli checkout).
  • Explicit: pass an exact semver as a positional argument (poli push 1.0.2). The server returns 409 VERSION_CONFLICT if the version already exists in any state.
poli push --message "Tweaked invoice header"        # patch (default), anchored on cloud.track
poli push --major --message "BREAKING: new schema"  # ignores track, picks max(major)+1
poli push --minor
poli push 1.0.2                                     # explicit version (no track logic)
poli push --track 1.0 --patch                       # override the manifest track (CI)

An explicit positional version is mutually exclusive with --patch / --minor / --major and with --track.

Hotfix flow — 1.0.1 LIVE in prod, 2.0.0 SANDBOX in dev:

poli checkout 1.0.1     # writes cloud.track = "1.0"
# fix the bug…
poli push --patch       # produces 1.0.2 (anchored on track 1.0), not 2.0.1
poli promote 1.0.2

After the push, cloud.track is updated to the major.minor of the version the server returned (no-op for patches on the same family).

poli versions list (alias ls)

Display all versions of the linked project with their state badges.

STATE       VERSION    PUSHED        MESSAGE
LIVE        2.0.0      3 days ago    Major redesign
LIVE        1.0.5      1 week ago    Hotfix URSSAF
SANDBOX     1.1.0      2 hours ago
DEPRECATED  1.0.2      3 weeks ago

poli promote <version>

Move a SANDBOX version to LIVE. Confirmation prompt unless --yes.

poli unpromote <version>

Move a LIVE version back to SANDBOX. Shows a usage preview (calls in the last 24h, the next-latest LIVE) before confirming.

poli unpromote 1.0.5
poli unpromote 1.0.5 --force    # allow unpromoting the last LIVE version

poli versions deprecate <version>

Mark a SANDBOX version as DEPRECATED.

poli versions un-deprecate <version>

Move a DEPRECATED version back to SANDBOX.

poli versions download <version> [-o <dir>]

Download a published version's content to a local directory (read-only inspection or hotfix base).

Render

poli render <spec>

Render a template against the cloud engine. The <spec> is either the bare template name (renders the draft) or name@<X.Y.Z> to pin an exact published version (npm-style). Every render produces a stored document (api-spec §11.3) — the CLI receives a JSON descriptor with documentId, presignedPdfUrl, expiresAt, and 13 other fields. By default it then fetches the URL and writes the PDF locally; pass --no-download to skip that step.

poli render invoice                                          # default: draft, downloads PDF
poli render [email protected]                                    # exact semver pin
poli render invoice@draft --data ./mock.json                 # explicit draft + custom data
poli render invoice -o ./out/invoice.pdf                     # custom path
poli render invoice --no-download                            # JSON descriptor only

Spec rules:

  • name → renders the draft (equivalent to name@draft).
  • name@<X.Y.Z> → exact semver only. latest, 1.0, etc. are rejected with a friendly error.

Flags:

  • --data <file> — JSON data overrides the mock
  • -o <file> — output PDF path. Incompatible with --no-download
  • --no-download — skip the presigned URL fetch (CI/CD pipelines)
  • --json — force JSON output even in a TTY

Output rules apply: in a TTY you see only the line, in a pipe you see only the JSON descriptor, with --json you always see the JSON. Pipelines stay clean:

$ DOC_ID=$(poli render invoice | jq -r '.documentId')
$ poli documents thumbnails "$DOC_ID" --width 400

Resolved environment is exposed in the success line (TTY):

✓ Rendered invoice v1.0.5 (live, billed) → ./output/invoice/invoice.pdf
✓ Rendered invoice vdraft (sandbox) → ./output/invoice/invoice.pdf

Counter:

  • version: draft or X.Y.Z SANDBOX/DEPRECATED → unmetered
  • version: X.Y.Z LIVE → counts in monthly quota

Documents

Documents are the cloud-stored output of every poli render invocation (or the same render endpoints called via the SDK or REST API). The CLI lets you inspect, delete, and re-derive thumbnails or preview HTML from them.

There is intentionally no poli documents list — your application is responsible for tracking the documentId values it cares about.

poli documents get <id>

Returns the document JSON descriptor — same 16-field shape as poli render. Output rules: in a TTY you see only the human-readable summary; in a pipe you see only the JSON descriptor; --json forces JSON. The CLI never downloads the PDF — curl the presignedPdfUrl or hand it to the browser yourself.

poli documents get doc_abc123
poli documents get doc_abc123 | jq '.presignedPdfUrl'   # pipe-friendly

poli documents delete <id>

Soft-deletes a document. Idempotent: returns success if already deleted. Confirmation unless --yes.

poli documents thumbnails <id>

Regenerate page thumbnails from the document's stored canonical HTML. Counts as one billable render in the monthly quota when the auth env is live. Free tier returns 403 THUMBNAILS_NOT_AVAILABLE.

poli documents thumbnails doc_abc123 --width 400
poli documents thumbnails doc_abc123 --pages 1,3 --format jpeg --quality 85
poli documents thumbnails doc_abc123 -o ./out

Flags: -w/--width, -f/--format png|jpeg, -q/--quality, --pages 1,3, -o/--output, --json.

poli documents preview <id>

Fetch the stored canonical HTML re-wrapped as preview-mode (free, no quota cost). Default writes the file at ./output/documents/<id>.preview.html and opens the browser.

poli documents preview doc_abc123
poli documents preview doc_abc123 --no-open
poli documents preview doc_abc123 -o ./preview.html

Manifest format

poli-page.json is the per-project manifest. Committed to git (no secrets).

{
  "project": {
    "name": "my-invoices",
    "version": "1.0.0"
  },
  "fonts": [
    {
      "family": "Inter",
      "src": "assets/fonts/Inter.woff2",
      "weight": 400
    }
  ],
  "templates": [
    {
      "name": "invoice",
      "template": "templates/invoice/invoice.html",
      "mock": "templates/invoice/invoice.json",
      "format": "A4",
      "orientation": "portrait"
    }
  ],
  "cloud": {
    "orgSlug": "acme",
    "orgId": "0e89d99e-1516-4b48-baa1-f49e660ad1c1",
    "projectSlug": "invoices",
    "projectId": "8274f64d-4ea2-4f96-b596-5727a7b17268"
  }
}

The cloud: block is written by poli link, removed by poli unlink. No API keys here — the credentials file (or POLI_PAGE_API_KEY) carries the secret.


Configuration

Environment variables

| Variable | Effect | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | POLI_PAGE_API_KEY | Single env var for any API key in CI/CD. Accepts pp_test_*, pp_live_*, or pp_sa_*. Must start with pp_. Precedence: when a credentials file is also present, the session wins. | | POLI_API_URL | Override the API base URL (precedence: --api-url flag → POLI_API_URLpoli-page.json cloud.apiUrl~/.polipage/credentials.json apiUrl → default https://api.poli.page). | | POLI_PAGE_ENV | Default render env (sandbox or live) when commands accept an env hint. | | NO_COLOR | Disable ANSI colors (Unix standard). | | CI | Auto-detected; equivalent to a global --yes. |

Common flags

  • --api-url <url> — override the API base URL for one invocation
  • -y / --yes — skip confirmation prompts
  • -h / --help — show help for any command or sub-command
  • -v / --version — print the CLI version and exit

Output conventions

Every command that returns a payload (renders, descriptors, lists, version mutations) follows the same output rule:

| Context | Stdout | | ------- | ------ | | TTY (interactive terminal), no --json | Human-readable summary | | Pipe / redirect / CI (stdout not a TTY) | JSON | | --json flag (any context) | JSON |

Either the summary or the JSON — never both at once. Errors always go to stderr with a clear message.

Commands that take this contract: render, documents get, documents preview, documents thumbnails, whoami, versions list, push, promote, unpromote, versions deprecate, versions un-deprecate.

Imperative commands (init, new, link, unlink, checkout, logout, documents delete, watch, login) don't have a JSON payload to forward — they just acknowledge with a line on success.


Exit codes

| Code | Meaning | | ---- | ------------------------------------------------------------------------------------------------------ | | 0 | Success (or no-op, e.g. promote when nothing changes) | | 1 | Recoverable inconsistency (already logged in, already linked, version already live) | | 2 | Invalid usage (bad args, template not found, version not found, non-TTY context where TTY is required) | | 3 | Network / API error (timeout, 5xx, unspecified 4xx) | | 4 | Not authenticated — run poli login or set POLI_PAGE_API_KEY | | 5 | Not authorized (insufficient role, THUMBNAILS_NOT_AVAILABLE, system project locked) | | 6 | Invalid local state (no project, project not linked, empty folder where required) |

These map to the centralised ExitCode constants in src/exit-codes.ts. Every handler routes errors through errorToExitCode(err).


CI / CD

GitHub Actions

# .github/workflows/render.yml
name: Render PDFs

on: workflow_dispatch

jobs:
  render:
    runs-on: ubuntu-latest
    env:
      POLI_PAGE_API_KEY: ${{ secrets.POLI_PAGE_API_KEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm install -g @poli-page/cli
      - run: poli render [email protected] -o ./artifacts/invoice.pdf
      - uses: actions/upload-artifact@v4
        with:
          name: invoice-pdf
          path: ./artifacts/invoice.pdf

Recommended: use a service account key (pp_sa_live_*) so rotating it does not log out any developer.

GitLab CI

render:
  image: node:22-alpine
  variables:
    POLI_PAGE_API_KEY: $POLI_PAGE_API_KEY
  script:
    - npm install -g @poli-page/cli
    - poli render [email protected] -o invoice.pdf
  artifacts:
    paths:
      - invoice.pdf

Vercel / Netlify build hook

# In a build script
npm install -g @poli-page/cli
POLI_PAGE_API_KEY=pp_sa_live_xxx poli render homepage-pdf -o ./public/welcome.pdf

Service accounts have a fixed role and environment at creation. The CLI does not try to switch them — the API enforces the permissions.


Troubleshooting

Not logged in. Run \poli login` or set POLI_PAGE_API_KEY.`

Either run poli login interactively, or export POLI_PAGE_API_KEY for CI use. Exit code 4.

POLI_PAGE_API_KEY must start with \pp_`...`

You set the variable but its value does not match the pp_* prefix. Rotate the key from the dashboard's API keys page. Exit code 4.

This folder isn't linked to a cloud project. Run \poli link` first.`

You're in session mode and the local manifest has no cloud: block. Either run poli link, or switch to api-key mode with POLI_PAGE_API_KEY=pp_* (which carries the org context implicitly). Exit code 6.

\latest` was retired. Run `poli versions list` and pin an exact semver...`

The CLI now requires an exact X.Y.Z semver when pinning a version. List the published versions with poli versions list and pick one. Exit code 2.

403 THUMBNAILS_NOT_AVAILABLE

Thumbnails via poli documents thumbnails are a paid-tier feature. Upgrade to Starter from the dashboard. Exit code 5.

429 OVERAGE_CAP_EXCEEDED

Your org has crossed the monthly cap on live PDF renders (counted across poli render against a LIVE version and poli documents thumbnails on a LIVE document). Wait until the next billing period or raise the cap from your dashboard. Exit code 3.

503 ORGANIZATION_MIGRATING

Your org is being migrated to a new tier. poli watch retries automatically after 5s. Other commands surface the error — retry by hand once the migration completes (typically under a minute).

poli watch doesn't fire on save

Make sure you're not editing inside an ignored directory. Default ignores: node_modules/, .git/, output/, dist/, *.log, .DS_Store. The watcher uses chokidar v5; if your editor saves via "atomic write + rename" (some Vim setups), the rename event fires within the debounce window and triggers a sync.

CI pipeline rejected with exit 5 on poli promote

Service-account developer keys cannot promote. Either use an admin SA, or run poli promote from a developer's interactive session. Service-account roles are fixed at creation.


License

MIT — see LICENSE.

Links