Docs: Templating Docs: CLI Pricing For Agencies Sign in Start Building
CLI reference

CLI

@clicklesscms/cli brings every page, partial, and collection entry template onto disk as plain HTML — editable in any editor, scriptable from any LLM, and deployable with versioned writes, drift detection, and typed-domain confirmations. The CLI never triggers a production deploy; promotion stays in the dashboard.

Install #

$ npm install -g @clicklesscms/cli
$ clickless --version

That's it. There's no service-account key to ship, no API token to mint up-front — the CLI authenticates as a real user via your browser the first time you run a command.

Requirements #

First login #

$ clickless login

  Opening your browser to authorize the ClickLess CLI…
  Listening on http://127.0.0.1:9005/callback (will time out in 120s)

  Signed in as Carson Suite <[email protected]>
  Session id:  cli_abc12345
  Credentials: OS keychain

The login flow uses PKCE (S256) plus a 256-bit CSRF nonce, a loopback-only listener on a free port in the 9005–9099 range, an Origin allowlist, and a hard 120-second timeout. See SECURITY.md for the full walkthrough.

Bind a folder to a site #

$ mkdir my-site && cd my-site
$ clickless init

  Pick an organization:
    1. The Crossing Church  (org_abc123, owner, 3 sites)
    2. Personal             (org_def456, 1 site)
  > 1

  Pick a website in "The Crossing Church":
    1. Main Site  (ws_xyz789, thecrossing.church)
    2. Donate     (ws_abcdef)
    3. Events     (ws_ghijkl)
  > 1

  You're about to bind this folder to:

    Organization: The Crossing Church
    Site:         Main Site
    Domain:       thecrossing.church
    Preview URL:  https://clickless.site/ws_xyz789

  Write clickless.json here? [Y/n] y

  Wrote /path/to/my-site/clickless.json
  Created .gitignore (added: clickless.json, .clickless/).
  Next: `clickless pull` to download pages, partials, and entry templates.

If you already know the org/site you want, skip the wizard: clickless use the-crossing-church/main-site.

First pull & deploy #

$ clickless pull
  … downloading 47 files (pages, partials, entry templates, schemas, docs) …
  Pull complete. 47 added, 0 conflicts.

$ vim pages/home.html      # or open in your favorite editor / LLM

$ clickless status
  On site:  Main Site  (thecrossing.church)
  Pulled:   2m ago

  Local changes (1 modified, 0 added, 0 archived):
    M  pages/home.html

  Cloud changes since last pull: none.

$ clickless deploy --dry-run
  … preview of what would change …
  DRY RUN — no changes were sent.

$ clickless deploy
  … typed-domain confirm + server write …
  Deployment cli_dep_xyz committed.
  Preview: https://clickless.site/ws_xyz789

Folder shape #

After clickless init and the first clickless pull, the project folder looks like this:

my-site/                              # bound to ONE website via clickless.json
├── clickless.json                    # the project link
├── .clickless/                       # CLI internal state (gitignored)
│   ├── snapshot/                     # verbatim cloud-state copy at last pull
│   │   ├── pages/
│   │   ├── partials/
│   │   ├── collections/
│   │   ├── docs/
│   │   └── manifest.json             # per-file hashes + cloud version ids
│   ├── lock                          # advisory file lock
│   └── audit.log                     # local timeline of commands
│
├── pages/                            # deployable HTML pages
│   ├── home.html
│   ├── home.meta.json                # read-only sidecar
│   ├── about.html
│   └── about.meta.json
│
├── partials/                         # deployable HTML partials
│   ├── header.html
│   ├── header.meta.json
│   ├── footer.html
│   └── footer.meta.json
│
├── collections/                      # one subfolder per collection
│   ├── staff/
│   │   ├── collection.meta.json      # schema (LLM context)
│   │   ├── template.html             # entry template (deployable)
│   │   └── template.meta.json
│   └── sermons/
│       ├── collection.meta.json
│       ├── template.html
│       └── template.meta.json
│
└── docs/                             # canonical reference docs (auto-refreshed)
    ├── template-field-guide.md
    └── clickless-cli-schema.md

Deployable vs read-only #

Path patternDeployable?Editable?
pages/*.html, pages/**/*.htmlYesYes
partials/*.htmlYesYes
collections/*/template.htmlYesYes
pages/*.meta.json, partials/*.meta.json, collections/*/template.meta.jsonNoNo (read-only in v1)
collections/*/collection.meta.jsonNoNo (schema context)
docs/**NoNo (auto-refreshed)
clickless.jsonNoNo (managed by CLI)
.clickless/**NoNo (internal state)
Any other file you addNoYes (CLI ignores it)

If you edit a .meta.json locally and run deploy, the change-set will report No deployable changes. That's expected — metadata edits stay in the dashboard for v1.

clickless.json — the project link #

This file is the single source of truth for what cloud target the folder is bound to. Every mutating command re-verifies the triple { organizationId, websiteId, domain } against the live cloud doc.

{
  "$schema": "https://clicklesscms.com/schemas/clickless.json/v1.json",
  "organizationId": "org_abc123",
  "organizationName": "The Crossing Church",
  "websiteId": "ws_xyz789",
  "websiteName": "Main Site",
  "domain": "thecrossing.church",
  "previewDomain": "clickless.site/ws_xyz789",
  "lastPulledAt": "2026-05-21T16:42:11Z",
  "lastPulledServerUpdatedAt": "2026-05-21T16:39:50Z",
  "cloudContentHash": "sha256:7d2f…",
  "files": {
    "pages/home.html": { "hash": "sha256:…", "cloudVersionId": "v_…" }
  },
  "cliVersion": "0.1.0"
}

Don't hand-edit organizationId, websiteId, or domain. Every mutating command re-verifies the triple against the live cloud doc and aborts on mismatch with no --force override. To switch the folder to a different site, run clickless use <org>/<site> — the CLI will re-fetch from the cloud and overwrite the link safely.

Because committing this file across branches can drift the deploy target between branches, clickless init and clickless use automatically add it (and .clickless/) to your project's .gitignore if you're in a git repo.

.clickless/ — internal state #

PathPurpose
.clickless/snapshot/Verbatim copy of the cloud state at last pull. Used for 3-way diff at deploy time. Do not edit.
.clickless/snapshot/manifest.jsonPer-file SHA-256 hashes and cloud version IDs for every pulled artifact.
.clickless/snapshot/docs/Hashes of the bundled docs at last pull. The next pull compares against this to detect whether a CLI upgrade shipped a newer guide.
.clickless/lockAdvisory file lock (PID + start time). Prevents two CLI commands from racing in the same folder. Stale > 10 min are auto-cleared.
.clickless/audit.logLocal timeline of CLI commands run in this folder.

Meta sidecars #

Every deployable file has a sibling *.meta.json sidecar carrying the cloud-side metadata. Sidecars are read-only locally in v1 — to change a title / slug / sortOrder, use the dashboard.

// pages/home.meta.json
{
  "$readOnly": true,
  "id": "page_xyz",
  "slug": "home",
  "title": "Home",
  "status": "published",
  "isHomePage": true,
  "is404Page": false,
  "sortOrder": 0,
  "parentPageId": null,
  "cloudVersionId": "v_abc"
}

Creating a new page: add pages/<name>.html with no .meta.json. On the next deploy, the server mints the new pageId + initial versionId and writes them into a fresh sidecar for you. The CLI never invents Firestore IDs.

"Deleting" a page: remove the .html file (and .meta.json if present). The next deploy sets the cloud doc's status to archived; the document and its versions are never hard-deleted. Recover via the dashboard or clickless versions.

Login flow #

The full PKCE + CSRF + loopback flow happens automatically when you run clickless login:

  1. CLI generates a 256-bit CSRF nonce and a 256-bit PKCE verifier; computes challenge = base64url(sha256(verifier)).
  2. CLI binds a single-shot HTTP listener on 127.0.0.1 on a free port between 9005 and 9099.
  3. CLI opens the user's browser to console.clicklesscms.com/cli-login with the port, state, and PKCE challenge as query parameters.
  4. The dashboard authenticates the user (via the existing Firebase Auth UI), calls createCliSession, and POSTs the returned token back to the loopback listener.
  5. CLI validates POST + path + Origin + state (constant-time) + body cap (≤ 16 KB), then exchanges the refresh token + PKCE verifier for a 15-minute Firebase custom token.
  6. Refresh token is persisted in the OS keychain (file fallback with chmod 600); the listener shuts down within ~200 ms of success or after a hard 120-second timeout.

clickless whoami #

$ clickless whoami

  Signed in as Carson Suite <[email protected]>
  UID:         f3aB2cD…
  Session id:  cli_abc12345  (5d old)
  Credentials: macOS Keychain
  Server:      session is valid.

Use --offline to skip the server check (useful on flaky networks), or --json for a machine-readable envelope.

clickless sessions #

List and revoke active CLI sessions for your account:

$ clickless sessions

  Active CLI sessions:

    cli_abc12345  *  active   created 5d ago   used 2m ago    macOS Terminal
    cli_def67890     active   created 12d ago  used 1d ago    GitHub Actions (CI)
    cli_ghi98765     active   created 28d ago  used 14d ago   Ubuntu / xterm

    `*` is the session this CLI is currently using.

$ clickless sessions revoke cli_ghi98765
  Revoked session cli_ghi98765.

clickless logout #

clickless logout revokes the current session server-side, then removes local credentials from keychain and the file fallback. Subsequent commands will prompt to log in again.

Headless / CI #

For CI runners, SSH sessions, and other browser-less environments, mint a long-lived CI token from a workstation that does have a browser:

$ clickless login:ci

  Minted CI session cli_abc12345 for [email protected].

  Save this token in your CI's secret store, then export it as $CLICKLESS_TOKEN:

    export CLICKLESS_TOKEN="cli_abc12345:RrTk…3pQ8"

  Then in your CI script, before running clickless:

    export CLICKLESS_AUTOMATION=1
    clickless deploy --force

  This token is valid for 90 days. Rotate via `clickless sessions revoke cli_abc12345`
  + a fresh `clickless login:ci`.

CI sessions are tagged with a (clickless-ci) userAgent marker — they show up in clickless sessions with a (CI) suffix so you can spot them and revoke them quickly.

Store $CLICKLESS_TOKEN in your CI's secret manager — never echo it to logs. If you suspect a token leak, revoke the session immediately via clickless sessions revoke <id> or the dashboard's "Connected CLIs" panel.

clickless pull — overview #

Downloads the active versions of every page, partial, and collection entry template for the bound site. Also refreshes collection schemas (read-only context) and bundled reference docs. Maintains .clickless/snapshot/ for 3-way diffs at deploy time.

Pull is idempotent. Running it twice in a row with no cloud changes is a no-op; running it after only local edits leaves your edits in place (see conflict handling below).

Conflict handling #

If both you and the cloud have changed the same file since the last pull:

The live pages/home.html is left untouched. Resolve in your editor or with an LLM, delete the three .local/.cloud/.base files, then re-run clickless pull to refresh the snapshot. Pull exits non-zero on any conflict so CI catches it.

Bundled docs refresh #

Two canonical reference documents ship with the CLI itself:

On every pull, if the bundled hash differs from the local hash, the local copy is overwritten with a one-line banner. Locally edited copies are silently overwritten — users wanting to fork a guide should keep their fork outside docs/.

clickless deploy — overview #

Pushes local changes to the bound site as new Firestore version documents, then triggers a preview build at clickless.site/{websiteId}. Never triggers a production deploy.

The flow runs every safety check in this order; any failure aborts before any write:

  1. Auth check + project-link parse.
  2. --force gate (if requested) — must be allowed by config or env var.
  3. Folder lock acquired.
  4. Triple-identifier verification against the live cloud doc.
  5. Per-artifact ownership check (every modified/archived id must still belong to this website).
  6. Cloud-bundle hash recomputed; drift surfaced if it doesn't match the snapshot.
  7. Local change-set built from .clickless/snapshot/.
  8. Typed-domain confirmation prompt (and ARCHIVE token if archiving any artifact).
  9. Server callable invoked with the validated payload.
  10. Per-artifact results written back into local sidecars + snapshot.
  11. Preview build triggered.

Dry-run #

clickless deploy --dry-run runs every step above through #7 (change-set summary) and then exits with a clear "DRY RUN — no changes were sent" footer. No server callable is invoked. --dry-run and --force are mutually exclusive.

Drift handling #

If the cloud has changed since your last pull:

  !! Cloud has changed since the last `clickless pull`.

     Local snapshot hash: sha256:7d2f4a…
     Current cloud hash:  sha256:a91f3b…

     Files changed on the cloud since your last pull:
       M  pages/about.html
       M  partials/footer.html
       +  pages/contact.html

  What would you like to do?
    [P]ull cloud changes first (recommended)
    [F]orce deploy and overwrite (creates new versions)
    [C]ancel

The [F]orce path requires --force AND the automation gate (see below). In non-interactive shells without --force, drift hard-aborts with CLOUD_DRIFT_DETECTED.

Confirmations #

Before any server write, deploy demands you type the site's domain back verbatim:

  You are about to deploy to PREVIEW:

    Organization:    The Crossing Church
    Site:            Main Site
    Site domain:     thecrossing.church
    Preview URL:     https://clickless.site/ws_xyz789

  To confirm, type the site domain exactly:
  > thecrossing.church

Matching is case-insensitive and whitespace-trimmed. Partial matches are rejected.

If the change-set includes any archives (deleted local files), deploy requires a second typed token: literally the string ARCHIVE.

Force gating #

--force bypasses the drift prompt AND the typed-domain confirmation — both. Because that's a lot of safety to skip, it's gated:

$ clickless config set automation.allow-force true      # persistent
# OR
$ CLICKLESS_AUTOMATION=1 clickless deploy --force       # per-command

Without one of those signals, --force in an interactive shell fails fast with FORCE_NOT_ALLOWED. This makes habit-typed --force a clean error rather than a silent overwrite.

Partial success #

Each artifact in a deploy is its own atomic batch on the server. If one fails (e.g. a permissions issue on a single page), the other artifacts continue and are committed. The CLI prints a per-artifact failure list and the server records the full breakdown in the cliDeployments audit doc:

  Deploying…

  1 artifact failed to deploy:
    x  page page_xyz (update) — permission-denied
  The audit doc captures full details; the rest of the deploy committed.

  Deployment cli_dep_abc12345 committed.

clickless status #

Git-style summary of local changes plus drift between the snapshot and the live cloud:

$ clickless status

  On site:  Main Site  (thecrossing.church)
  Pulled:   3h ago

  Local changes (2 modified, 0 added, 0 archived):
    M  pages/home.html
    M  partials/footer.html

  Cloud changes since last pull (1 modified):
    M  pages/about.html

  Run `clickless diff` to inspect local changes, then `clickless deploy`.
  Cloud has moved since the last pull. Run `clickless pull` to refresh.

--offline skips the cloud round-trip. --json emits the full structured changeset.

clickless diff #

Unified diff against the snapshot, or against the live cloud version with --cloud:

$ clickless diff pages/home.html
$ clickless diff pages/home.html --cloud
$ clickless diff                              # diff every modified file (cap 25)

If $PAGER is set and stdout is a TTY, output is piped through it. --no-pager disables.

clickless versions #

List the version history of a single artifact:

$ clickless versions pages/home.html --limit 5

  pages/home.html  →  pages/page_xyz
  Currently active: v_abc123

    v_abc123      *  2026-05-21 09:14   [email protected]   cli         "Deploy from CLI session cli_xyz789"
    v_def456         2026-05-20 16:39   [email protected]    dashboard   "Hero copy tweak"
    v_ghi789         2026-05-20 11:22   [email protected]   ai-editor   "Make the headline more punchy"
    v_jkl012         2026-05-18 14:01   [email protected]  dashboard   "Initial setup"
    v_mno345         2026-05-15 09:00   [email protected]  migration   "Migrated from legacy content field"

  Showing 5 versions. Use --limit to see more.

For artifacts that no longer exist locally (e.g. archived pages), pass --kind page --id <cloudId> instead of a path.

clickless restore #

Pull a specific historical version back to your working tree. Does not deploy — it's a working-tree change only. Run clickless deploy afterwards to make the restored version active in cloud.

$ clickless restore pages/home.html --version v_def456

  Overwrite pages/home.html with version v_def456? (Working-tree change only.) [y/N] y
  Restored pages/home.html to v_def456.
  This is a working-tree change only. Run `clickless deploy` to make it active in cloud.

clickless deployments #

Recent CLI deploys for the bound site:

$ clickless deployments --limit 5

  Recent CLI deploys for thecrossing.church:

    2026-05-21 09:14   cli_dep_abc12345     pages:3 partials:1 templates:0   sha256:7d2f4a→sha256:a91f3b
    2026-05-20 16:42   cli_dep_def67890     pages:1 partials:0 templates:0   sha256:a91f3b→sha256:3b4c1d   (force)
    2026-05-19 11:30   cli_dep_ghi98765     pages:0 partials:2 templates:1   sha256:3b4c1d→sha256:f2e9a0
    …

  For full per-artifact detail, see the dashboard's "Recent CLI deployments" panel.

clickless config #

$ clickless config list
$ clickless config get automation.allow-force
$ clickless config set automation.allow-force true

The config file lives at ~/.config/clickless/config.json (or %APPDATA%\clickless\config.json on Windows; honors $XDG_CONFIG_HOME). It carries automation.allow-force, ui.color, and a few other knobs.

clickless open #

Open the dashboard for the active site in your default browser. --print-url skips the spawn step for SSH sessions and just prints the URL.

clickless preview #

Open the preview URL (clickless.site/{websiteId}) for the active site. Useful right after a deploy.

clickless doctor #

One-shot health check. Run this whenever something feels off — it's the first thing support will ask you for.

$ clickless doctor

  PASS  Auth: signed in as [email protected]
  PASS  Project link: 1 (thecrossing.church)
  PASS  Triple-identifier check: org / website / domain all match cloud
  PASS  Snapshot integrity: 47 of 47 files match their recorded hash
  PASS  No nested project link in parent directories
  WARN  Metadata edits detected (pages/home.meta.json edited 2h ago); meta is read-only — use the dashboard
  PASS  CLI 0.1.0 (latest on npm: 0.1.0)
  PASS  Network reachable (https://console.clicklesscms.com responded)

  doctor: 7 pass / 1 warn / 0 fail.

JSON envelope #

Every read command (projects, status, diff, versions, restore, deployments, sessions, doctor, config, open, preview, whoami, login:ci) supports --json and emits the canonical envelope on both success and failure paths:

// success
{ "ok": true,  "data": { /* command-specific payload */ } }

// failure
{ "ok": false, "error": { "code": "FORCE_NOT_ALLOWED", "message": "…" } }

This makes the CLI safe to script against — branch once on ok, no per-command special-casing.

Tenant isolation #

The CLI enforces tenant isolation end-to-end. Every project folder is bound to a single website, every mutating command re-verifies that binding against the live cloud doc, and every server callable re-checks ownership independently. Highlights:

Typed-domain confirmation #

Before any server write, you have to type the site's domain back exactly. Case-insensitive, whitespace-trimmed, no partial matches. The only way to skip is --force with automation enabled.

ARCHIVE second-token #

If your change-set includes any deleted local files (which become status: 'archived' on the cloud doc), deploy demands a second typed token: literally ARCHIVE. This makes accidental rm or branch-checkout-induced deletions a non-issue.

No production deploy #

The CLI cannot trigger a production deploy. There's no code path for it; a CI grep test fails the build if anyone tries to import one. Promotion to production stays a dashboard-only action — by construction, not by convention.

Versioned writes #

Every CLI deploy creates new version documents in Firestore — old versions are never deleted. Archives flip status: 'archived' rather than hard-deleting. The shared versionedWrite helper makes the parent-doc update + version-doc write atomic via a single Firestore batch, populates an explicit activeVersionId pointer, and migrates legacy pre-versioning docs on first write.

Even a --force deploy is non-destructive at the data layer — you can roll back any artifact via clickless versions + clickless restore.

Doctor pass / warn / fail #

The 8 doctor checks classify their findings as PASS / WARN / FAIL. The command exits non-zero iff any FAIL surfaces, so CI scripts can clickless doctor as a precondition for clickless deploy.

Common patterns:

Revoke a session #

If a CI token leaks, or a laptop is lost, revoke the session immediately. Two channels:

Revocation is server-side immediate. The next call from that session returns unauthenticated and the CLI prompts to log in again.

FORCE_NOT_ALLOWED #

You passed --force without enabling the automation gate. Fix:

$ clickless config set automation.allow-force true      # persistent
# OR (for one-off CI invocations)
$ CLICKLESS_AUTOMATION=1 clickless deploy --force

CLOUD_DRIFT_DETECTED #

Someone (or another writer) changed the site between your last pull and this deploy. The safe path is to pull and re-deploy:

$ clickless pull           # may surface .local/.cloud/.base conflict files
$ clickless deploy

If you're certain you want to overwrite the cloud (the new versions stay recoverable), pass --force.