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

zinnector

v0.1.6

Published

Zinnector — the Zinn Digital developer CLI. Scaffold a WordPress site, run it locally with no Docker required, pre-flight it against the slot you are about to deploy to, and push. Built on the public Zinn Digital API.

Readme

Zinnector

The Zinn Digital developer CLI. Build a WordPress site on your own machine with nothing installed but Node, check it against the hosting slot you are about to deploy to, and push.

npm install -g zinnector

Free and MIT-licensed. zinnector new and zinnector dev need no account at all.


Requirements

| you need | why | |---|---| | Node 24 or newer — node --version | the CLI is written for it and refuses to start on anything older (exit code 78) | | npm — it comes with Node | used once, to download the WordPress runtime into Zinnector's own cache | | git, only for zinnector push | deploys are driven from your repository | | Docker, only if you ask for --runtime docker | the default runtime needs no Docker at all |

Install Node 24 (LTS): nodejs.org/en/download — on Windows winget install OpenJS.NodeJS.LTS, on macOS brew install node@24.

Seen npm WARN EBADENGINE while installing? That is npm telling you your Node is older than the >=24.18.1 this package declares. The install still completes, but the CLI will not run — zinnector --version prints "Zinnector needs Node 24 or newer" and exits 78. Upgrade Node and run npm install -g zinnector again.

On Node 26 or newer? The CLI itself runs fine. The WordPress runtime it downloads has a native module whose prebuilt binaries only exist for Node 24 and 25, so on first use Zinnector also fetches a Node 24 (about 30 MB, verified against nodejs.org's checksums) into its cache and runs the runtime on that. No compiler, no Visual Studio, nothing to do by hand. See Troubleshooting for what it looked like before 0.1.2.


Test it works — two minutes, no account

Run these in order. What each one should print is under it; if yours differs, the Troubleshooting section names the fix.

1. The version. Confirms the binary is on your PATH and your Node is new enough.

zinnector --version
0.1.3

The exact number is whatever npm currently serves — npm view zinnector version is the authority, and this README is not. What matters is that it prints something: if the command is not found, the install did not complete.

2. The help. Every command, exactly as the reference below lists them.

zinnector --help
Zinnector — the Zinn Digital developer CLI

Build WordPress locally with nothing installed but Node, check it against the
hosting slot you are about to deploy to, and push.

  $ zinnector new my-site      scaffold a project
  $ zinnector dev              run it locally (no Docker required)
  …
Commands:
  new [options] [name]        scaffold a WordPress project you can run immediately
  dev [options]               run this project locally — no Docker required
  …

3. Scaffold a site. A project that boots immediately — no placeholder files.

zinnector new my-site
Created my-site  ·  site
──────────────────────────

  + .gitignore
  + README.md
  + wp-content/mu-plugins/000-zinnector-dev.php
  + wp-content/plugins/.gitkeep
  + wp-content/themes/.gitkeep
  + zinnector.json

  php        8.3
  wordpress  latest
  runtime    playground

→ run it:
    cd my-site
    zinnector dev

4. Run it. The first time, Zinnector asks once before downloading the WordPress runtime (about 570 MB — every PHP build from 5.2 to 8.5, kept in its own cache, never in your project). Answer Y, or pass --yes.

cd my-site
zinnector dev
● the local playground needs its WordPress runtime

  package  @wp-playground/[email protected]
  size     ~570 MB, once — every PHP build from 5.2 to 8.5
  into     ~/.cache/zinnector/runtimes/playground/3.1.51
  needs    nothing else. No Docker, no MAMP, no PHP on your PATH.

  install it now? [Y/n] y
✔ WordPress runtime installed

my-site  ·  WordPress Playground (WASM, no Docker)
───────────────────────────────────────────────────

  serving    a WordPress project
  from       /home/you/my-site
  php        8.3
  wordpress  latest

✔ running at http://127.0.0.1:9400
  admin: http://127.0.0.1:9400/wp-admin  (already signed in)

  Ctrl-C to stop

5. Open it. Visit http://127.0.0.1:9400 in a browser: a fresh WordPress site titled My WordPress Website, served by PHP 8.3. http://127.0.0.1:9400/wp-admin opens the dashboard already signed in. Drop a plugin into wp-content/plugins/ or a theme into wp-content/themes/ and it is there on the next reload — the runtime serves your project's directories directly.

6. Prove the runtime is what it says. --once boots, prints what is actually running, and stops — the same measurement zinnector check --probe uses.

zinnector dev --once
✔ runtime reported:
  php         8.3
  wordpress   not reported
  extensions  intl, redis, memcached

Everything above works without an account. The rest needs an API key — see Your API key — and a site on Zinn Digital hosting.


The flow: create → dev → link → check → push

zinnector new my-site      # 1. scaffold a project that boots immediately
cd my-site
zinnector dev              # 2. a real WordPress, locally, with no Docker

zinnector login            # 3. once, when you have hosting
zinnector link             # 4. point the project at a slot (and connect its repository)
zinnector check            # 5. pre-flight: what will NOT match on the slot?
zinnector push             # 6. push your commits, deploy, watch it finish

zinnector new prints exactly this list when it finishes, so you never have to remember it.

Why the middle one matters

You built on PHP 8.3. The slot runs 7.4. Nothing tells you until the site is white.

$ zinnector check

Pre-flight — example.com  ·  01a03f39-730a-7dc4-84b0-5d4f7582dd04
──────────────────────────────────────────────────────────────────

  local   php 8.3   1,284 files, 12.4 MB
  slot    php 7.4.33   target fleet
  local php is the version DECLARED in zinnector.json — run with --probe to measure it

  ✖ high      php 8.3 locally, 7.4 on the slot
      You built against PHP 8.3, which is newer than the PHP 7.4 the
      slot runs. Code that uses syntax or functions added after the
      slot's version will fatal at runtime, not at deploy time.
      try zinnector php 8.3

  ⚠ warn      the slot runs php 7.4, which is past end of life
      An end-of-life PHP receives no security fixes. The platform keeps
      grandfathered versions running rather than forcing a migration,
      but new sites cannot select one.
      try zinnector php --list

  ⚠ warn      the slot cannot switch to php 8.3
      Your local version is not offered for this site's product line.
      The slot offers: 7.4, 8.0, 8.1.
      try zinnector dev --php 7.4

  ? unknown   local php extensions could not be enumerated
      try zinnector dev --once

  5 passed  ·  3 to look at  ·  2 could not be checked

  none of this blocks a deploy — zinnector push --force proceeds anyway

It warns; it never blocks. Every finding is overridable. You know things about your own site that a checker does not, and a tool that refuses to deploy is a tool people uninstall.

"Could not check" is its own outcome. A check that cannot see its subject reports unknown, never pass. That is why the summary says "2 could not be checked" instead of telling you everything is fine.

| check | against | |---|---| | PHP version | major gaps graded high, minor gaps lower — grading everything critical teaches people to skip the warning | | PHP switchable | whether the slot can even move to the version you built on | | PHP end of life | whether the slot runs something unsupported | | PHP applied | whether the machine has confirmed the version, or merely been told | | disk | your project's size against the headroom actually left, not against the plan | | file count | separately — a WordPress tree can exhaust its inode allowance while well under its disk cap | | WordPress | your working copy against the live install, and which way round | | provisioning | whether the slot has finished being built | | deploy path | whether a git repository is connected to push to |


Two local runtimes, both real

playground (default) — WordPress Playground: PHP compiled to WebAssembly, running inside Node. No Docker, no MAMP, no PHP on your PATH. Any PHP from 5.2 to 8.5.

docker — php-fpm and MariaDB containers. Slower, needs a daemon, and runs a native PHP, so it is the honest answer when what you need to test is an extension the WASM build does not carry. PHP 7.4 to 8.5.

zinnector dev                          # playground
zinnector dev --runtime docker         # native PHP + MariaDB
zinnector dev --php 8.1 --wp 6.7       # develop against a specific pair
zinnector dev --once                   # boot, report what is running, stop
zinnector dev --reset-runtime          # throw the installed runtime away and install it again

Both serve the same files from the same project, so switching between them changes the engine and nothing else. Neither is a fallback for the other — Docker has to be asked for by name, because a developer who thinks they are on WASM and is actually on Docker has been given the wrong answer by a tool trying to help.

The Playground runtime is downloaded on first use (~570 MB, once, into ~/.cache/zinnector/ — or ZINNECTOR_CACHE_DIR) rather than shipped as a dependency: @php-wasm alone is 456 MB because it carries every PHP build from 5.2 to 8.5. npm i -g zinnector stays small, and a developer who only ever runs zinnector sites never downloads any of it. The download is done by your own npm, run as node npm-cli.js — never through a shell, so a cache path with a space in it is still one path.


Command reference

Generated from the CLI's own command definitions — the same objects zinnector --help prints from — so this section cannot disagree with the tool you have installed. Outputs marked captured were pasted from a real run of this version; the others show the exact shape the command prints, with example values.

Every command takes -h, --help. Global options, accepted before any command:

| option | what it does | |---|---| | -V, --version | print the version | | --profile <name> | use a named credential profile |

zinnector new

Scaffold a WordPress project you can run immediately.

zinnector new [options] [name]

| argument | required | what it is | |---|---|---| | name | no | directory to create the project in, and the project's name — required unless --here |

| option | what it does | default | |---|---|---| | -t, --template <kind> | one of: site, plugin, theme | "site" | | --php <version> | PHP version to develop against | "8.3" | | --wp <version> | WordPress version for the local runtime | "latest" | | --runtime <name> | playground or docker | "playground" | | --here | scaffold into the current directory instead of a new one | |

Example

zinnector new my-site

What you see — captured — 0.1.2 on Linux

Created my-site  ·  site
──────────────────────────

  + .gitignore
  + README.md
  + wp-content/mu-plugins/000-zinnector-dev.php
  + wp-content/plugins/.gitkeep
  + wp-content/themes/.gitkeep
  + zinnector.json

  php        8.3
  wordpress  latest
  runtime    playground

→ run it:
    cd my-site
    zinnector dev

→ then, when you have a hosting slot:
    zinnector link     # point it at a site
    zinnector check    # pre-flight before you push
    zinnector push

zinnector dev

Run this project locally — no Docker required.

zinnector dev [options]

| option | what it does | default | |---|---|---| | --php <version> | PHP version to run (5.2–8.5 on playground, 7.4–8.5 on docker) | | | --wp <version> | WordPress version | | | -p, --port <port> | port to serve on | | | --runtime <name> | playground (default) or docker | | | --login | log in as admin automatically | true | | --no-login | do not log in automatically | | | --once | boot, report what is running, then stop | | | --yes | install the local runtime without asking | | | --reset-runtime | delete the installed runtime and install it again | | | -v, --verbose | show the runtime's own output | |

Example

cd my-site
zinnector dev

What you see — captured — 0.1.2. The first run asks once to download the WordPress runtime (~570 MB); pass --yes to skip the question

my-site  ·  WordPress Playground (WASM, no Docker)
───────────────────────────────────────────────────

  serving    a WordPress project
  from       /home/you/my-site
  php        8.3
  wordpress  latest

✔ running at http://127.0.0.1:9400
  admin: http://127.0.0.1:9400/wp-admin  (already signed in)

  Ctrl-C to stop

zinnector login

Sign in — with your own account in the browser, or with an API key.

zinnector login [options]

| option | what it does | default | |---|---|---| | --profile <name> | store under a named profile (default: default) | | | --api <url> | API base URL | "https://api.zinndigital.com" | | --api-key | paste a per-organisation API key instead of signing in as yourself | | | --issuer <url> | the sign-in service | "https://auth.zinndigital.com/realms/zinn" | | --no-browser | print the sign-in link without trying to open a browser | |

Example

zinnector login

What you see — the key is typed at the prompt, never on the command line. In CI, pipe it: echo "$ZINN_API_KEY" | zinnector login --profile ci

Sign in to Zinn Digital  ·  https://api.zinndigital.com
────────────────────────────────────────────────────

  API key: ••••••••••••••••••••••••••••

✔ signed in as [email protected] (Acme Ltd) — profile “default”
→ next: zinnector sites

zinnector logout

Forget a stored sign-in.

zinnector logout [options]

| option | what it does | default | |---|---|---| | --profile <name> | which profile to forget (default: default) | |

Example

zinnector logout

What you see

✔ signed out of profile “default”

zinnector whoami

Show who this CLI is signed in as, and what it can reach.

zinnector whoami [options]

| option | what it does | default | |---|---|---| | --profile <name> | use a named profile | | | --scopes | list the permissions an API key carries | | | --json | machine-readable output | |

Example

zinnector whoami --scopes

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Signed in  ·  https://api.zinndigital.com
──────────────────────────────────────────
account        [email protected]
as             a person, signed in through the browser
organisations  8 — listed below
mode           live
credential     profile “default”

  · Example Org  owner · 01a00000-0000-7000-8000-000000000000
  · Example Org· 01a00001-0000-7000-8000-000000000001
  · Example Org· 01a00002-0000-7000-8000-000000000002
  · Example Org· 01a00003-0000-7000-8000-000000000003
  · Example Org· 01a00004-0000-7000-8000-000000000004
  · Example Org· 01a00005-0000-7000-8000-000000000005
  · Example Org· 01a00006-0000-7000-8000-000000000006
  · Example Org· 01a00007-0000-7000-8000-000000000007

  scopes belong to API keys — a signed-in person acts with their ROLE in each organisation (shown above).

zinnector sites

List the sites this key can see.

zinnector sites [options]

| option | what it does | default | |---|---|---| | --json | machine-readable output | | | --status <status> | only sites in this status | |

Example

zinnector sites

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Sites  ·  12
─────────────
domain          status  php      target  id
example-2.com   active  php —    fleet   01a00000-0000-7000-8000-000000000000
example-3.com   active  php —    fleet   01a00001-0000-7000-8000-000000000001
example-4.com   active  php —    fleet   01a00002-0000-7000-8000-000000000002
example-5.com   active  php 8.5  fleet   01a00003-0000-7000-8000-000000000003
example-6.com   active  php —    fleet   01a00004-0000-7000-8000-000000000004
example.com     active  php 8.4  fleet   01a00005-0000-7000-8000-000000000005
example-7.com   active  php —    fleet   01a00006-0000-7000-8000-000000000006
example-8.com   active  php 8.5  fleet   01a00007-0000-7000-8000-000000000007
example-9.com   active  php 8.5  fleet   01a00008-0000-7000-8000-000000000008
example-10.com  active  php 8.5  fleet   01a00009-0000-7000-8000-000000000009
example-11.com  active  php 8.5  fleet   01a00010-0000-7000-8000-000000000010
example-12.com  active  php 8.5  fleet   01a00011-0000-7000-8000-000000000011

zinnector status

Everything the platform knows about one site.

zinnector status [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector status example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


example.com  ·  01a00000-0000-7000-8000-000000000000
────────────────────────────────────────────────────────────────────────
status         active
product line   footprint_free
stack          wordpress
region         fsn1
deploy target  fleet
plan           PBN 10

● PHP
version        8.4
can switch to  8.5, 8.4, 8.3, 8.2, 8.1, 8.0, 7.4

● Quota
disk      188 MB used of 500.0 GB
files     6,313 of 2,000,000
database  1 MB

● Deploy
status         building
target         fleet
repository     —
commit         —
live url       —
last deployed  2026-09-14T01:29:26.991511Z

● WordPress
version     7.1
up to date  yes
multisite   no

zinnector share

Give one person access to ONE site — a developer, a designer, a client.

zinnector share [options] <site> <email>

| argument | required | what it is | |---|---|---| | site | yes | the site to share — an id, primary domain or name | | email | yes | who to give access to — they are emailed an invitation if they have no account yet |

| option | what it does | default | |---|---|---| | --role <role> | what they may do: viewer, editor, manager | | | --reason <text> | why they need it — kept with the grant for the audit trail | | | --until <date> | when access ends on its own (YYYY-MM-DD); omit for until-revoked | | | --json | machine-readable output | |

Example

zinnector share example.com [email protected] --role editor --reason "rebuild the checkout"

What you see — the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ

Shared example.com  ·  01a03f39-730a-7dc4-84b0-5d4f7582dd04
────────────────────────────────────────────────────────────

✔ invited [email protected] as editor
  They have no account yet, so an invitation has been emailed. Until they
  sign in and accept it they can see nothing; the moment they do, the site is theirs.

→ what they do next, on their own machine:
    npm i -g zinnector && zinnector login   # sign in as themselves
    zinnector clone example.com   # pull the files down

zinnector collaborators

Who has been given access to a site, and whether they have accepted.

zinnector collaborators [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector collaborators example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ


Access to example.com  ·  3
───────────────────────────────────────────────────────────
who              role    status   ends  why                                          grant
[email protected]  viewer  revoked  —     Example Org— revoked immediately after       01a00000-0000-7000-8000-000000000000
[email protected]  viewer  revoked  —     Example Org— revoked at the end of the walk  01a00001-0000-7000-8000-000000000001
[email protected]  editor  revoked  —     Example Org— revoked at the end of the walk  01a00002-0000-7000-8000-000000000002

zinnector unshare

Take a person's access to a site back — by email or grant id.

zinnector unshare [options] <site> <who>

| argument | required | what it is | |---|---|---| | site | yes | the site to take access back on — an id, primary domain or name | | who | yes | the person to remove — their email address, or the grant id from zinnector collaborators |

| option | what it does | default | |---|---|---| | --yes | do not ask | |

Example

zinnector unshare example.com [email protected] --yes

What you see — the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ

✔ withdrew the invitation to [email protected]

zinnector invitations

The invitations waiting for you, and accept one.

zinnector invitations [options] [command]

Example

zinnector invitations

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ; with no subcommand it runs list


Invitations  ·  0
──────────────────

● nothing waiting for you

zinnector invitations list

Invitations addressed to the account you are signed in as.

zinnector invitations list [options]

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector invitations list

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ


Invitations  ·  0
──────────────────

● nothing waiting for you

zinnector invitations accept

Accept an invitation — the site is yours the moment this returns.

zinnector invitations accept [options] <id>

| argument | required | what it is | |---|---|---| | id | yes | the invitation id from zinnector invitations — a unique prefix of it is enough |

Example

zinnector invitations accept 01a03f39-9c11-7b02-a7e0-2e4d0c5f1b77

What you see — the exact shape the command prints, from the source (W40-DEVTOOL added these after this file was last captured); your domains, ids and dates will differ

✔ you now have access to example.com

zinnector link

Point this project at a hosting slot (and connect its repository).

zinnector link [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --repo <owner/name> | connect this repository to the site on the platform | | | --provider <name> | the connected account to deploy through: github_app, github or gitlab (default: the one your organisation has connected) | | | --branch <name> | branch a push deploys from | | | --no-repo | only write zinnector.json; do not touch the platform | |

Example

zinnector link example.com --repo acme/example-site --branch main

What you see

✔ this project deploys to example.com (01a03f39-730a-7dc4-84b0-5d4f7582dd04)
  written to zinnector.json — commit it so everyone deploys to the same slot
✔ the platform will deploy acme/example-site (main)
→ next: zinnector check

zinnector pull

Pull a hosted site's files into this project for local dev — and re-run to re-sync.

zinnector pull [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --via <seam> | auto (default), git, or backup | "auto" | | --fresh | take a new backup rather than using the latest one (spends an on-demand slot) | | | --max-age <hours> | accept the latest backup if it is younger than this | "24" | | --full | the whole document root, WordPress core included (default: wp-content only) | | | --no-uploads | leave wp-content/uploads (media) behind | | | --live-config | also write wp-config.php — it holds the LIVE database credentials | | | --clean | empty wp-content before extracting, so deletions on the site are mirrored | | | --dry-run | say what would happen and stop | | | --yes | do not ask | |

Example

zinnector pull

What you see

Pull example.com  ·  01a03f39-730a-7dc4-84b0-5d4f7582dd04
─────────────────────────────────────────────────────────────

● via git — the site syncs to its own repository and this is a clone of it
✔ fast-forwarded 3 commits 4f1c2a9b → 9d2e7c14

● not synced, and why
  ✖ database                     the local playground has its own; the site's is not copied
  ✖ offloaded media              objects in storage are not in the site's files
  ✖ anything outside the repository  only wp-content syncs

→ next: zinnector dev

zinnector clone

Make a local project from a hosted site: clone its repository or pull its files.

zinnector clone [options] <site> [dir]

| argument | required | what it is | |---|---|---| | site | yes | the hosted site to copy — an id, primary domain or name | | dir | no | directory to create — defaults to the site's primary domain |

| option | what it does | default | |---|---|---| | --via <seam> | auto (default), git, or backup | "auto" | | --fresh | take a new backup rather than using the latest one | | | --full | the whole document root, WordPress core included | | | --no-uploads | leave wp-content/uploads (media) behind | | | --live-config | also write wp-config.php — it holds the LIVE database credentials | | | --yes | do not ask | |

Example

zinnector clone client.example.com

What you see

Clone client.example.com  ·  client.example.com
──────────────────────────────────────────────────────

✔ wrote client.example.com/zinnector.json (php 8.3, wordpress 6.7.1)
● via backup — the site does not sync to a repository
✔ downloaded 214 MB and extracted wp-content

● not synced, and why
  ✖ database                     the local playground has its own; the site's is not copied
  ✖ offloaded media              objects in storage are not in the site's files
  ✖ wp-config.php                holds the live database credentials (--live-config to write it)
  ✖ WordPress core               the runtime supplies the version the slot runs (--full to take it)

→ next: zinnector dev

→ run it:
    cd client.example.com
    zinnector dev

zinnector sync

Two-way sync between a site's live files and its repository.

zinnector sync [options] [command]

Example

zinnector sync --help

What you see — with no subcommand, zinnector sync <site> is sync status

Usage: zinnector sync [options] [command]

two-way sync between a site's live files and its repository

  site → repository   zinnector sync run   commits the live files and pushes the branch
  repository → site   zinnector push       builds the branch and publishes it

zinnector sync status

Where two-way sync stands, and its last runs.

zinnector sync status [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector sync status example.com

What you see — captured from a real run of 0.1.4 against a test site on 2026-09-12; the domain and ids are replaced with example values

Two-way sync — example.com
──────────────────────────

repository   acme/example-site (main)
available    yes
sync         on
state        synced
last sync    2026-09-12T22:51:41.539832Z
note         Started from acme/example-site@main.

run        files       commit         when
synced     2 changed   da85d73cef5f   2026-09-12T22:52:10.117301Z

zinnector sync on

Turn two-way sync on (pushes nothing by itself).

zinnector sync on [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --subdir <path> | only sync this subdirectory of the site | | | --json | machine-readable output | |

Example

zinnector sync on example.com

What you see — needs a repository connected on your own GitHub account; it pushes nothing by itself

✔ two-way sync is on

Two-way sync — example.com
…
→ next: zinnector sync run

zinnector sync off

Turn two-way sync off.

zinnector sync off [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --subdir <path> | only sync this subdirectory of the site | | | --json | machine-readable output | |

Example

zinnector sync off example.com

What you see

✔ two-way sync is off

zinnector sync run

Commit the site's live files to its repository now.

zinnector sync run [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector sync run example.com

What you see — captured from a real run of 0.1.4 against a test site on 2026-09-12; the domain and ids are replaced with example values. A run the repository has moved past is HELD instead: nothing is pushed or overwritten, the command exits 1 and names the next step

✔ committed 2 changed files da85d73cef5f

Two-way sync — example.com
…
→ next: git pull in your clone

zinnector sync resolve

Clear a held sync by choosing which side wins.

zinnector sync resolve [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --keep <side> | site (commit the site on top) or repository (discard the site's unsynced changes) | | | --yes | do not ask | | | --json | machine-readable output | |

Example

zinnector sync resolve example.com --keep repository --yes

What you see — captured from a real run of 0.1.4 against a test site on 2026-09-12; the domain and ids are replaced with example values. Without --yes it asks first, and refuses outright when there is no terminal to ask in

✔ kept the repository

Two-way sync — example.com
…
state        synced

zinnector check

Compare this project against the slot you are about to deploy to.

zinnector check [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --probe | boot the local runtime and MEASURE its php, rather than trusting zinnector.json | | | --all | show passing checks too | | | --strict | also fail when something could not be checked | | | --json | machine-readable output | |

Example

zinnector check

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted — the run found a real mismatch (PHP 8.3 in the project against 8.4 on the slot), which is the whole point of the command

→ reading the slot
  size excludes: .git

Pre-flight — example.com  ·  01a00000-0000-7000-8000-000000000000
────────────────────────────────────────────────────────────────────────

  local  php 8.4  3 files, 0.0 MB
  slot   php 8.4  target fleet
  local php is the version DECLARED in zinnector.json — run with --probe to measure it

  ? unknown   local php extensions could not be enumerated
      try zinnector dev --once

  ? unknown   wordpress versions could not be compared
      No WordPress version found in the working copy.
      try zinnector status

  7 passed  ·  2 could not be checked

zinnector push

Pre-flight, push your commits, and deploy.

zinnector push [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | -f, --force | deploy even when pre-flight has findings | | | --skip-checks | do not run pre-flight at all | | | --probe | measure the local runtime during pre-flight instead of trusting zinnector.json | | | --no-wait | trigger the deploy and exit without watching it | | | --no-git | do not run git push; deploy whatever the platform already has | | | --dry-run | do everything except the deploy | |

Example

zinnector push

What you see

Deploy to example.com  ·  01a03f39-730a-7dc4-84b0-5d4f7582dd04
───────────────────────────────────────────────────────────────

✔ pre-flight: 8 passed  ·  0 to look at  ·  2 could not be checked
→ pushing main (4f1c2a9b add the contact form)
✔ pushed
● deploy 7c0e… started — watching
✔ deployed 4f1c2a9b in 48s

  status    success
  commit    4f1c2a9b
  url       https://example.com

zinnector deploy

Redeploy what the platform already has, without pushing.

zinnector deploy [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --no-wait | trigger and exit | |

Example

zinnector deploy example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Redeploy example.com  ·  01a00000-0000-7000-8000-000000000000
────────────────────────────────────────────────────────────────────────
→ starting the deploy
✔ deploy started

zinnector deploys

A site's deploy history.

zinnector deploys [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | | | -n, --limit <n> | how many to show | "10" |

Example

zinnector deploys example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Deploys — example.com  ·  2
───────────────────────────────────────────────────────────
status  commit  branch  trigger  started                      message
live    —       —       manual   2026-09-14T09:06:39.685554Z  —
live    —       —       manual   2026-09-14T01:29:20.196957Z  —

zinnector php

Show or switch a site's PHP version.

zinnector php [options] [version]

| argument | required | what it is | |---|---|---| | version | no | the PHP version to switch the site to, e.g. 8.4 — omit to show the current one |

| option | what it does | default | |---|---|---| | -s, --site <site> | site id or domain | | | --list | list the versions the platform offers | | | --json | machine-readable output | |

Example

zinnector php 8.3 --site example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. with no version it shows the current one; --list shows what the platform offers for this site's product line


PHP — example.com
──────────────────────────────────────────────────────
version        8.4
can switch to  8.5, 8.4, 8.3, 8.2, 8.1, 8.0, 7.4

zinnector quota

Disk, files and database usage against what the plan grants.

zinnector quota [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector quota example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Quota — example.com
────────────────────────────────────────────────────────
disk used      188 MB
disk limit     500.0 GB
disk granted   500 GB  (per_site)
files used     6,313
file limit     2,000,000
database       1 MB

  “limit” is what is enforced today; “granted” is what the plan says.

zinnector db

Database size, and single-use links into phpMyAdmin and the file manager.

zinnector db [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector db example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. the phpMyAdmin and file-manager links are single-use and expire; do not paste them anywhere


Database — example.com
───────────────────────────────────────────────────────────
size    1 MB
tables  14

  single-use links, valid for one visit:
  phpMyAdmin    https://pma.example.com/signon.php?t=<single-use>
  file manager  https://files.example.com/signon.php?t=<single-use>

zinnector logs

Tail a site's access and error logs.

zinnector logs [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --access | only the access log | | | --error | only the error log | | | --build | the build log of the most recent deploy instead | | | --json | machine-readable output | |

Example

zinnector logs example.com --error

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. --access for the access log, --build for the last deploy's build log (a site not built by a build provider shows the deploy's own steps); with no flag both site logs are shown, and a log that could not be read says so instead of looking empty


Error log — example.com
────────────────────────────────────────────────────────────

2026-09-14 11:06:07.153643 [NOTICE] [1929978] [T0] [config:vhost:zcaffb6eec5abbdab03fb9b2d62a15:epsr:lsphp-zcaffb6eec5abbdab03fb9b2d62a15]'Process Limit' probably is too low, adjust the limit to: 640.
2026-09-14 11:06:37.873060 [INFO] [1929982] [T0] [lsphp-zcaffb6eec5abbdab03fb9b2d62a15] add child process pid: 1933036, procinfo: 0x5555561cfe30

zinnector wp

Run an allow-listed WP-CLI command on a site.

zinnector wp [options] [command...]

| argument | required | what it is | |---|---|---| | command... | no | the WP-CLI command and its arguments, e.g. plugin list — omit it with --commands |

| option | what it does | default | |---|---|---| | -s, --site <site> | site id or domain | | | --commands | list what the platform will run | | | --json | machine-readable output | |

Example

zinnector wp plugin list --site example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. the allow-list is served by the platform — zinnector wp --commands prints it

name	status	update	version	update_version	auto_update
akismet	inactive	none	5.7.2		off
hello	inactive	none	1.7.2		off
imunify-security	active	none	4.1.0		off
litespeed-cache	active	none	7.9.1		off
redis-cache	active	none	2.8.0		off
zinn-cache	active	none	1.2.2		off
object-cache.php	dropin				off

zinnector backup

List and take site backups.

zinnector backup [options] [command]

Example

zinnector backup list example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. backup on its own prints its subcommands; see backup list and backup now below


Backups — example.com  ·  3
───────────────────────────────────────────────────────────
status  taken                        size    id
stored  2026-09-14T09:06:39.918280Z  4.7 MB  01a00000-0000-7000-8000-000000000000
stored  2026-09-14T01:29:20.403161Z  36 MB   01a00001-0000-7000-8000-000000000001
stored  2026-09-14T01:29:15.898099Z  36 MB   01a00002-0000-7000-8000-000000000002

daily backups   on
retention       30 days
off-site copy   yes
on-demand left  2 of 3

zinnector backup list

List a site's backups and what its plan grants.

zinnector backup list [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector backup list example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Backups — example.com  ·  3
───────────────────────────────────────────────────────────
status  taken                        size    id
stored  2026-09-14T09:06:39.918280Z  4.7 MB  01a00000-0000-7000-8000-000000000000
stored  2026-09-14T01:29:20.403161Z  36 MB   01a00001-0000-7000-8000-000000000001
stored  2026-09-14T01:29:15.898099Z  36 MB   01a00002-0000-7000-8000-000000000002

daily backups   on
retention       30 days
off-site copy   yes
on-demand left  2 of 3

zinnector backup now

Take an on-demand backup.

zinnector backup now [options] [site]

| argument | required | what it is | |---|---|---| | site | no | a site id, primary domain or name — defaults to the site linked in zinnector.json |

Example

zinnector backup now example.com

What you see

✔ backup started — zinnector backup list example.com shows it when it completes

zinnector domains

List the domains this key can see.

zinnector domains [options]

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector domains

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Domains  ·  1
──────────────
domain       status     ownership  renewal        expires
example.com  connected  connected  no auto-renew  —

zinnector dns

Read and change DNS records.

zinnector dns [options] [command]

Example

zinnector dns list example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. dns on its own prints its subcommands; see dns list, dns add and dns rm below


DNS — example.com  ·  1 records
────────────────────────────────────────
type  name          value               ttl
TXT   w4357-verify  zinn-w4357-updated  ttl 3600

zinnector dns list

List a domain's DNS records.

zinnector dns list [options] <domain>

| argument | required | what it is | |---|---|---| | domain | yes | the domain, by name or id |

| option | what it does | default | |---|---|---| | --type <type> | only records of this type | | | --json | machine-readable output | |

Example

zinnector dns list example.com

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


DNS — example.com  ·  1 records
────────────────────────────────────────
type  name          value               ttl
TXT   w4357-verify  zinn-w4357-updated  ttl 3600

zinnector dns add

Create a DNS record.

zinnector dns add [options] <domain> <type> <name> <value>

| argument | required | what it is | |---|---|---| | domain | yes | the domain, by name or id | | type | yes | record type: A, AAAA, CNAME, MX, TXT, SRV, … | | name | yes | the record name — @ for the domain itself, or a subdomain label | | value | yes | what the record points at — an address, a hostname or text |

| option | what it does | default | |---|---|---| | --ttl <seconds> | time to live | "3600" | | --priority <n> | priority, for MX and SRV | | | --proxied | route through the CDN | | | -y, --yes | do not ask | |

Example

zinnector dns add example.com TXT _acme "token" --ttl 300

What you see — the record is shown and confirmed before it is written; pass --yes to skip the question

Add a DNS record to example.com
───────────────────────────────

  type   TXT
  name   _acme
  value  "token"
  ttl    300

  create it? [y/N] y
✔ created TXT _acme

zinnector dns rm

Delete a DNS record.

zinnector dns rm [options] <domain> <recordId>

| argument | required | what it is | |---|---|---| | domain | yes | the domain, by name or id | | recordId | yes | the record id, as zinnector dns list prints it |

| option | what it does | default | |---|---|---| | -y, --yes | do not ask | |

Example

zinnector dns rm example.com rec_7f3a

What you see

⚠ deleting A @ 203.0.113.10 from example.com
  delete it? [y/N] y
✔ deleted

zinnector mail

List mail services.

zinnector mail [options]

| option | what it does | default | |---|---|---| | --json | machine-readable output | |

Example

zinnector mail

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted


Mail services  ·  0
────────────────────

● no mail services

zinnector bulk

Run one operation across many sites (cache_purge, malware_scan, sitemap, block_ip).

zinnector bulk [options] <operation>

| argument | required | what it is | |---|---|---| | operation | yes | what to run on every selected site: cache_purge, malware_scan, sitemap or block_ip |

| option | what it does | default | |---|---|---| | --all | every site this key can see | | | --site <site...> | specific sites, by id or domain | | | --status <status> | every site in this status | | | -y, --yes | do not ask | | | --json | machine-readable output | |

Example

zinnector bulk cache_purge --all

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. one call to the platform for the whole selection, not one per site; the selection is shown and confirmed first


Bulk cache_purge  ·  1 sites
─────────────────────────────

·   example.com

→ running cache_purge
✔ cache_purge accepted for 1 sites

  The platform runs this asynchronously; results appear per site in the dashboard.

zinnector ai

Ask the Zinn Digital assistant, with this project as context.

zinnector ai [options] <question...>

| argument | required | what it is | |---|---|---| | question... | yes | what you want to ask, in plain words — quoting it is optional |

| option | what it does | default | |---|---|---| | -s, --site <site> | ask about a particular site | | | --org <id> | the organisation to ask as, when your account belongs to more than one | | | --no-context | do not send anything about the local project | | | --json | machine-readable output | |

Example

zinnector ai "why is my deploy failing?"

What you see — captured from a real run of 0.1.5 against production on 2026-09-14; domains, ids and single-use links substituted. The assistant answers in the ACCOUNT's language — this account's locale is Arabic, so the captured answer is Arabic to an English question

→ thinking

Zinn Digital assistant
────────────────────────────

  هذا مشروع WordPress تم إنشاؤه بواسطة Zinnector® (مُهيّأ على PHP 8.4، أحدث
  إصدار WordPress، وبيئة تشغيل playground)؛ لتشغيله محلياً، انتقل إلى مجلده
  وشغّل الأمر: cd <اسم-المشروع> ثم zinnector dev، وسيقوم بتشغيل موقع WordPress
  حقيقي على جهازك دون الحاجة إلى Docker.

  sources:
    · https://zinndigital.com/kb/zinnector-getting-started
    · https://zinndigital.com/kb/zinnector-local-development

zinnector mcp

Run a stdio bridge to the hosted MCP server — for Claude Desktop and other local-only clients.

zinnector mcp [options]

Example

ZINN_MCP_TOKEN=zmcp_live_… zinnector mcp

What you see — started BY an AI client such as Claude Desktop, never by hand — it speaks MCP on stdin and stdout. Proven from Claude Code 2.1.270 against the live server on 2026-09-12

zinnector 0.1.4: MCP bridge to https://api.zinndigital.com/v1/mcp

Working on a site somebody shared with you

An owner can give you one site — not their whole account — from the site's Security tab, or with zinnector share. You then work on it as yourself:

npm install -g zinnector
zinnector login                       # a code appears; sign in with YOUR account in the browser
zinnector sites                       # only the sites shared with you
zinnector clone client.example.com    # the files, into ./client.example.com
cd client.example.com && zinnector dev

If the owner shared the site before you had an account, zinnector login shows the invitation and accepts it for you. Everything you do is recorded under your name, and the owner can take the access back at any time — effective on your next command, whatever token you hold.

What you can do is the role you were given: viewer reads the site, its logs and its backups list; editor adds deploys, cache, PHP version, plugins, themes, wp-admin and zinnector pull; manager adds restores, SSL, CDN and imports. No role can delete the site, see billing, or reach anything else the owner hosts — a request that names any other site answers 404.


Pulling a site's files down

zinnector pull                   # into the linked project; run it again to re-sync
zinnector pull --fresh           # take a new backup first
zinnector pull --no-uploads      # skip media
zinnector clone example.com      # a new project from a hosted site

Two ways, and the tool picks the honest one:

  • git — the site syncs to its own repository (Deploy → Two-way sync, free) and this project is a clone of it. The platform commits the live files, then pull fast-forwards. Exact and incremental. A held sync (the repository and the site both moved) stops here with both commits named; nothing is overwritten on either side.
  • backup — otherwise. The newest backup (or a fresh one) is downloaded and wp-content is taken out of it, into the same wp-content-shaped project zinnector new creates.

What is NOT synced — printed on every run: the database (the local playground has its own), media offloaded to object storage, wp-config.php (live credentials; --live-config if you really need it) and WordPress core (the runtime supplies the slot's version; --full to take it).


Giving a developer access

zinnector share client.example.com [email protected] --role editor --reason "Theme rebuild" --until 2026-12-31
zinnector collaborators client.example.com
zinnector unshare client.example.com [email protected]

They see that site and nothing else you host. No account yet? They are emailed an invitation and the site is theirs the moment they accept.

Scripting and CI

Human output goes to stderr, data goes to stdout, so --json pipes cleanly:

zinnector sites --json | jq -r '.[].primary_domain'
- run: npm install -g zinnector
- run: zinnector check --strict
  env:
    ZINNECTOR_TOKEN: ${{ secrets.ZINN_API_KEY }}

zinnector check exits non-zero when it finds something, so it works as a pipeline gate. --strict also fails when a comparison could not be made at all.

Exit codes: 0 success · 1 a finding or a refusal · 70 an internal error · 77 authentication or permission · 78 unsupported Node.

Environment: ZINNECTOR_TOKEN · ZINNECTOR_API · ZINNECTOR_DEBUG=1 · ZINNECTOR_CACHE_DIR · ZINNECTOR_CONFIG_DIR · NO_COLOR


Signing in

Two ways in, and they mean different things:

zinnector login                             # as YOURSELF — a code, a browser, your account
zinnector login --api-key                   # with a per-organisation API key, from a prompt
echo "$ZINN_API_KEY" | zinnector login      # or from stdin, for CI

Signing in as yourself is what a developer working on a shared site does: the CLI then sees exactly the sites that were shared with you, and every action is recorded under your name. An API key belongs to a whole organisation and is never narrowed to one site — right for CI and for scripting your own estate, wrong for a contractor. Create one under Settings → API keys.

The key is never read from the command line, so it cannot end up in your shell history or in a process listing. It is stored in your config directory with file mode 600, and Zinnector re-asserts those permissions on every write.

Keys are scoped: one can never carry a permission its creator does not hold. zinnector whoami --scopes shows what yours carries.


zinnector.json

Committed, so a colleague who clones the repository deploys to the same place without being told. It holds no secret.

{
  "name": "my-site",
  "site": "01a03f39-730a-7dc4-84b0-5d4f7582dd04",
  "php": "8.3",
  "wordpress": "latest",
  "runtime": "playground",
  "port": 9400
}

Troubleshooting

spawn EINVAL right after accepting the runtime install (Windows). A bug in Zinnector 0.1.0 and 0.1.1: on Windows the installer ran npm.cmd by name, which Node has refused to spawn without a shell since its fix for CVE-2024-27980. Fixed in 0.1.2 — npm is now run as node npm-cli.js, the same on every platform. npm install -g zinnector@latest and run zinnector dev again.

npm WARN EBADENGINE on install, or Zinnector needs Node 24 or newer. Your Node is older than 24. Install the current LTS from nodejs.org and reinstall Zinnector. node --version must print v24 or higher.

gyp ERR! / "You need to install Visual Studio" / "Building from source with node-gyp" while the runtime installs. Your Node has no prebuilt binary for the runtime's native module (Node 26 and newer, as of September 2026). Fixed in 0.1.2: Zinnector fetches a Node 24 for the runtime alone and never compiles anything. If you still see it, run zinnector dev --reset-runtime so the half-finished install is thrown away first.

EPERM warnings from npm while it cleans up (Windows). Usually an antivirus or an indexer holding a file open under node_modules. The install is retried on the next run; zinnector dev --reset-runtime starts from a clean directory.

"the local playground exited before it was ready" or never becomes ready. Run zinnector dev --verbose to see the runtime's own output. Port in use? zinnector dev --port 9401. A runtime that installed but will not boot: zinnector dev --reset-runtime.

Behind a proxy or a registry mirror. The runtime is fetched with your own npm, so npm config set proxy … / registry … apply. The Node 24 download (Node 26+ only) comes from https://nodejs.org/dist/ and honours HTTPS_PROXY.

Still stuck? Run the failing command with ZINNECTOR_DEBUG=1 and open an issue at github.com/Zinn-Digital/zinnector/issues with the output, your node --version and your operating system.


Licence

MIT. Built on the public Zinn Digital API — every command is a documented operation in the OpenAPI spec, so nothing here can do something the API cannot.

Zinn Digital® Ltd · zinndigital.com