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.
Maintainers
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 zinnectorFree and MIT-licensed. zinnector new and zinnector dev need no account at all.
- Docs on the web: zinndigital.com/zinnector · full command reference · knowledge base
- Source and issues: github.com/Zinn-Digital/zinnector
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 EBADENGINEwhile installing? That is npm telling you your Node is older than the>=24.18.1this package declares. The install still completes, but the CLI will not run —zinnector --versionprints "Zinnector needs Node 24 or newer" and exits78. Upgrade Node and runnpm install -g zinnectoragain.
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 --version0.1.3The exact number is whatever npm currently serves —
npm view zinnector versionis 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 --helpZinnector — 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-siteCreated 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 dev4. 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 stop5. 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, memcachedEverything 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 finishzinnector 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 anywayIt 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 againBoth 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/— orZINNECTOR_CACHE_DIR) rather than shipped as a dependency:@php-wasmalone is 456 MB because it carries every PHP build from 5.2 to 8.5.npm i -g zinnectorstays small, and a developer who only ever runszinnector sitesnever downloads any of it. The download is done by your own npm, run asnode 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-siteWhat 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 pushzinnector 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 devWhat 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 stopzinnector 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 loginWhat 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 siteszinnector logout
Forget a stored sign-in.
zinnector logout [options]| option | what it does | default |
|---|---|---|
| --profile <name> | which profile to forget (default: default) | |
Example
zinnector logoutWhat 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 --scopesWhat 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 sitesWhat 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-000000000011zinnector 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.comWhat 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 nozinnector 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 downzinnector 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.comWhat 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-000000000002zinnector 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] --yesWhat 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 invitationsWhat 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 youzinnector 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 listWhat 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 youzinnector 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-2e4d0c5f1b77What 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.comzinnector 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 mainWhat 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 checkzinnector 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 pullWhat 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 devzinnector 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.comWhat 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 devzinnector sync
Two-way sync between a site's live files and its repository.
zinnector sync [options] [command]Example
zinnector sync --helpWhat 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 itzinnector 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.comWhat 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.117301Zzinnector 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.comWhat 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 runzinnector 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.comWhat you see
✔ two-way sync is offzinnector 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.comWhat 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 clonezinnector 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 --yesWhat 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 syncedzinnector 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 checkWhat 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 checkedzinnector 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 pushWhat 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.comzinnector 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.comWhat 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 startedzinnector 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.comWhat 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.comWhat 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.4zinnector 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.comWhat 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.comWhat 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 --errorWhat 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: 0x5555561cfe30zinnector 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.comWhat 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 offzinnector backup
List and take site backups.
zinnector backup [options] [command]Example
zinnector backup list example.comWhat 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 3zinnector 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.comWhat 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 3zinnector 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.comWhat you see
✔ backup started — zinnector backup list example.com shows it when it completeszinnector domains
List the domains this key can see.
zinnector domains [options]| option | what it does | default |
|---|---|---|
| --json | machine-readable output | |
Example
zinnector domainsWhat 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.comWhat 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 3600zinnector 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.comWhat 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 3600zinnector 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 300What 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 _acmezinnector 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_7f3aWhat you see
⚠ deleting A @ 203.0.113.10 from example.com
delete it? [y/N] y
✔ deletedzinnector mail
List mail services.
zinnector mail [options]| option | what it does | default |
|---|---|---|
| --json | machine-readable output | |
Example
zinnector mailWhat 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 serviceszinnector 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 --allWhat 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-developmentzinnector 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 mcpWhat 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/mcpWorking 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 devIf 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 siteTwo 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
pullfast-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-contentis taken out of it, into the samewp-content-shaped projectzinnector newcreates.
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 CISigning 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
