WPicker (a playful nod to the hardworking woodpecker 🐦) lets you develop a WordPress site on your own machine — with or without an AI agent — and move the work to the live site with one command, instead of the usual backup → upload → restore routine.
It has two halves:
| Runs on | Does | |
|---|---|---|
| Plugin | the WordPress site | Receives changes, checks them, keeps snapshots and backups |
CLI (wpicker) |
your computer | Pulls, pushes, merges, clones |
Warning
Use it on staging, development, or local sites, or on production only with backups you have tested. Deactivate and remove the plugin when you are done with it.
| Every push is a snapshot you can inspect, revert or restore |
|---|
![]() |
| Full-site backup and restore, or restore from a file |
![]() |
| Scan installed plugins for known vulnerabilities |
![]() |
Contents: Quick start · Daily work · Working as a team · Undoing things · Backup & restore · AI agents · Command reference · Limits
- Download
wpicker.zipfrom the latest release. - In WP-Admin go to Plugins → Add New Plugin → Upload Plugin, choose the zip, Install Now, then Activate.
- A WPicker menu appears in the sidebar.
The site needs:
- PHP 7.4 or newer, with the
phpcommand available to the web server (used to syntax-check pushed files). - An active child theme, if you want to sync theme files. WPicker only ever writes inside the child theme — never the parent theme, core, or
wp-config.php. - HTTPS (or
WP_ENVIRONMENT_TYPEset tolocal): WordPress only hands out Application Passwords on secure sites, and WPicker pairs through one. - The
edit_themescapability for whoever approves a device (on multisite: a super administrator). A site that setsDISALLOW_FILE_EDITremoves it from everybody.
macOS / Linux:
curl -sL https://raw.githubusercontent.com/febritecno/wpicker/main/install.sh | bash -s -- install
wpicker --helpThe installer also connects the AI agents it finds on your computer (Claude Code, OpenCode, Codex, Gemini CLI, Goose), so /wpicker works in Claude Code right away. Skip that with bash -s -- install --no-agents.
wpicker connect does the same at any time. Global is the default, and you can pick which agents:
wpicker connect # every agent found on this computer, for all your projects
wpicker connect claude opencode # just those (same as --agent claude,opencode)
wpicker connect --agent all # every adapter
wpicker connect cursor # a tool with only per-project rules: written into this project
wpicker connect claude --project # this project only
wpicker connect list # what is connected, current or stale
wpicker connect remove claude # take WPicker out againBuilt in: claude, opencode, codex (AGENTS.md), gemini, goose, cursor, copilot, windsurf, cline, roo, zed, kiro. The locations and formats of these tools' rule files are from memory of their conventions, not checked against each tool's current documentation; if one stops working, tell us (each is one line in cli/cmd/connect.go).
Your own adapters. Anything else that reads an instruction file can be connected without changing WPicker: drop a JSON file in ~/.wpicker/adapters/ (never read from a project, so a cloned repository cannot add one):
{"key": "mytool", "name": "My Tool",
"global": ".mytool/instructions.md", "project": "MYTOOL.md",
"mode": "block", "content": "stub", "detect": ["~/.mytool"]}mode is block (a marked section in a file you also write in) or file (a file of WPicker's own, with optional frontmatter: skill, cursor or windsurf); content is stub (a short pointer) or guide (the full guide). Paths must be relative and stay inside their folder; a bad file is ignored with a reason in wpicker connect list and wpicker doctor.
Safety: the instruction files are only written inside the project (never through a symbolic link that leads out of it), a file you wrote is never replaced without --force, and login asks before touching a project. Run connect as yourself, not with sudo. Your pairing is per user, not per project, so when you open a repository you do not trust, do not let your agent run wpicker freely there: allow only the read-only commands (map, logs, find, files, db, guide) and keep theme push and rollback on ask.
Agents that read a file in every conversation get a short pointer and fetch the full instructions with wpicker guide only when the task is about the site. Files you already have (AGENTS.md, GEMINI.md) only get a marked block added. Run wpicker connect again after upgrading to refresh.
Change install to remove to uninstall. To build from source instead (Go 1.21+):
git clone https://github.com/febritecno/wpicker && cd wpicker
make build-cli # → bin/wpickerKeep the plugin and the CLI on the same version; wpicker login warns when they differ.
wpicker login https://your-site.comIt prints a link and opens it in your browser. There you log in to WordPress as you always do (so your password and any two-factor plugin apply), see which site and which device are asking, check that the code matches the one in your terminal, and press Approve. The terminal then finishes by itself. Nothing is typed into it, and your password never reaches the CLI.
- Only approve a request you started yourself, a moment ago, on your own computer. Never approve a link somebody sent you. When the request came from a different network than your browser (for example, you ran the command on a server over SSH), WP-Admin makes you type the code from that terminal before it lets you approve.
- Add
--no-browserto only print the link. The plugin must be 1.3.0 or newer; the older pairing with an admin password and a PIN has been removed, so update the plugin and the CLI together. - Behind a proxy or CDN (Cloudflare, a load balancer, some managed hosts): WordPress then sees every visitor as one address, so WPicker cannot tell whether the request came from your network, and always asks you to type the code.
wpicker doctorsays when this applies. If your proxy sets the real address in a header and strips it from outside requests, tell WPicker:add_filter( 'wpicker_client_ip', fn() => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ); - Typing the code is a speed bump against someone who sends you a link, not a wall: a determined phisher can simply tell you the code. Do not approve requests you did not start.
- Approving needs the
edit_themescapability (on multisite: a super administrator). On a site that setsDISALLOW_FILE_EDIT, WordPress removes that capability from everyone, and no pairing method of WPicker works until it is lifted. - The CLI keeps a separate, revocable credential for this device in
~/.wpicker/sites.json(readable only by you); your own password is never stored. WordPress emails you when a device is paired, and WP-Admin → WPicker → Devices shows who just joined, with a Revoke it button. - You can pair several sites; logging in to another one does not touch the first. See them with
wpicker sites. - Check which site a folder uses with
wpicker whoami, or ask the site to describe itself withwpicker context. - Remove a site any time with
wpicker sites delete <id>(it also revokes this computer on the site), or from WP-Admin → WPicker → Devices.
Each project folder works with one site. wpicker theme pull links the folder to the site it pulled from (a small .wpicker/project.json, no secrets), and from then on every command in that folder uses that site. For any other folder:
wpicker sites # id, name, address, user, status, linked folders
wpicker sites link shop # use "shop" in this folder from now on
wpicker --site blog logs # one command against another site
wpicker sites stop blog # keep its login but refuse to use it (start to undo)
wpicker sites delete blog # forget it and revoke this computer on itWith one site nothing needs choosing. With several, commands that only look (logs, map, files) fall back to your default site (wpicker sites use <id>) and say so on stderr; commands that change a site (theme push, rollback, page, restore) refuse to guess and ask you to link the folder or pass --site. --site takes an id, name or address of a site you are already logged in to; it can no longer point your login at another address.
Upgrading: your existing login is picked up automatically as site 1. ~/.wpicker/config.json is left untouched, so an older wpicker binary on your PATH keeps working with it.
Pick one.
A. The whole site — database, plugins, themes, uploads. This is the one that replaces "export a backup, import it locally".
Create an empty WordPress locally first (for example with Local), then:
wpicker clone --path "/path/to/local/wordpress"--path is the folder containing wp-config.php. The command backs the site up, downloads one archive, unpacks it, loads the database and rewrites every address to your local one. Your previous local database and folders are saved to wpicker-before-restore-<time>/ first. Afterwards you log in locally with the same accounts as on the site.
Database settings are read from the local wp-config.php (sites made with Local are detected automatically). Override them if needed: --db-host, --db-port, --db-socket, --db-name, --db-user, --db-pass, and --url for the local address.
B. Only the child theme — when you just want to work on theme code.
mkdir my-child-theme && cd my-child-theme
wpicker theme pullAfter a clone you already have the theme: cd into wp-content/themes/<child-theme> and carry on from there.
Run these inside the child theme folder.
wpicker theme status # what would be pushed, what could be pulled, conflicts
# ...edit files, check them on your local site...
wpicker theme push # send your changes to the siteWhat push does on the site, in order:
- Syntax check every PHP file. An error rejects the push; nothing is written.
- Snapshot the theme as it is now.
- Write your files (and delete the ones you deleted).
- Health check: load the site. If it now returns a server error, the snapshot is put back automatically and you get the PHP error with file and line.
So a bad push does not take the site down, and you are told what to fix.
To pick up changes made on the site:
wpicker theme pull # lists them; you choose which to applyChanges on https://example.com not in your local copy:
1) A inc/helpers.php
2) M style.css
3) C functions.php (changed on both sides — merged line by line; …)
Apply which? [a]ll (merging where needed) · numbers (1,3 or 2-4) · d <n> to view diff · [q]uit >
wpicker theme diff [file] shows the line-by-line difference between your copy and the site.
Several people (or agents) can be paired with the same site. WPicker never lets one silently overwrite another.
If someone pushed while you were editing, your push is refused and status says who:
Conflicts (changed on both sides):
C inc/shop.php (ani-laptop, 2026-10-13 14:02)
Run wpicker theme pull. Files changed on both sides are merged line by line, like git:
-
Edits to different lines are simply combined.
-
Where you both changed the same lines, both versions are kept between markers:
<<<<<<< yours return 'IDR '; ======= return 'Rp'; >>>>>>> site (ani-laptop, 2026-10-13 14:02)Open the file — VS Code, Cursor and JetBrains show their merge controls for these — settle each block, delete the marker lines, and push. A push is refused while markers remain. Your pre-merge version is in
.wpicker/merge-backup/.
To take one side outright instead of merging:
wpicker theme pull --theirs functions.php # the site's version
wpicker theme pull --keep-local functions.php # yours; the next push overwrites the site'sImages, fonts and other binary files cannot be merged; you choose a side.
Review before it goes live. In WP-Admin → WPicker → Settings, set When a device pushes to Review first. Pushes are then held: an administrator opens Snapshots, reads the diff, and clicks Apply to site or Reject. A device cannot approve its own push.
Every push is recorded under WP-Admin → WPicker → Snapshots (search, filter, and a side-by-side diff of each file). From there, or from the CLI:
| Action | What it does |
|---|---|
| Revert this push | Undoes that one push. Later pushes are kept. |
Restore to before this (wpicker rollback <id>) |
Puts the whole theme back to how it was before that entry. Later pushes are undone too. |
wpicker snapshots lists them. If a revert collides with a later change, you decide per file which version stays.
WP-Admin → WPicker → Backup & Restore — full-site backups, each a single .tar file.
- Create a backup of the database, plugins, themes and uploads.
- Restore any listed backup onto the site. The database is loaded into temporary tables first and switched over at the end, and the current site is backed up before anything changes.
- Restore from a file — upload a backup made on another site. Addresses are rewritten to the destination's automatically, so this is also how you move a whole site from one place to another.
From the CLI:
wpicker backup list
wpicker backup create --note "before redesign"
wpicker backup restore --path "/path/to/local/wordpress" # pick one, rebuild it locally
wpicker clone --path "/path/to/local/wordpress" # create + restore in one goRestoring replaces the destination's content and user accounts with the backup's; you log in again with an account from the backup.
Off by default. Turn on Diagnostic access in WP-Admin → WPicker → Settings; a paired administrator device (you, or your AI agent) can then look at the site without changing it:
wpicker logs --since 1h # PHP + WordPress errors, grouped, with file and line
wpicker debug on # record every warning, notice and deprecation…
wpicker logs --since 5m --trace # …reproduce the problem, then read them
wpicker debug off
wpicker map # what is installed: plugins, themes, WooCommerce gateways, email plugins
wpicker find woocommerce_thankyou --kind hook # where a function, class, hook or REST route is defined
wpicker logs --source wc:stripe # WooCommerce and payment-gateway logs (new level: error)
wpicker files ls plugins/my-plugin # any code under wp-content
wpicker files cat plugins/my-plugin/main.php --lines 80-120
wpicker files grep "add_action( 'init'" plugins
wpicker db tables
wpicker db query "SELECT ID, post_title, post_status FROM wp_posts ORDER BY ID DESC LIMIT 10"- Logs come from PHP's error log,
wp-content/debug.log, and WPicker's own log.wpicker debug ondoes whatWP_DEBUG+WP_DEBUG_LOGdo — everything is logged, nothing is shown to visitors — without editingwp-config.php. - Secrets are masked by their shape, not only by the name next to them: payment keys, bearer tokens, JWTs, card numbers (checked with the Luhn test, so order numbers stay readable) and CVCs, including inside
print_rdumps and in lines cut by the length limit. The names of log files are never sent. - Map and find read an index of the site's PHP (functions, classes, methods, hooks, shortcodes, REST routes, post types, options), kept per file so a second search takes milliseconds.
wpicker mapmarks plugins that are the site's own, and a plugin that is a symbolic link out ofwp-content(it cannot be read there, and says so). - Files are read-only and limited to
wp-content.wp-config.php, backups, key files and database dumps are not available. - Database queries are read-only:
SELECT,SHOW,DESCRIBE,EXPLAIN, one statement, run in a read-only transaction. Credentials are out of reach — the password columns do not exist for these queries, rows holding keys, tokens, salts or sessions are left out, and tables that are credential stores by name are refused.
Everything else in the database can be read, including customer and personal data, and whatever an AI agent reads is sent to its provider. Each access is listed under Settings → Recent diagnostic access. Switch it off when you are done.
There is nothing to set up per project for the agents that have a global setting. The installer (or wpicker connect) teaches Claude Code, OpenCode, Codex, Gemini CLI and Goose about WPicker, so /wpicker works in Claude Code right away. For a tool that only has per-project rules (Cursor, Copilot, Windsurf, Cline, Roo, Zed, Kiro), run wpicker connect <name> in the project; wpicker login offers to do it when it finds one there. wpicker update refreshes the instruction files WPicker wrote, so they never go stale, and wpicker guide prints the full instructions an agent follows.
- Every command takes
--jsonfor machine-readable output. wpicker contextgives the agent the facts it would otherwise guess: WordPress and PHP versions, active theme, plugins, theme settings.- A failed push writes
.wpicker/last-error.jsonwith the error code, message, file and line, so the agent can fix and retry on its own. - Without a terminal,
wpicker theme pullapplies everything and merges where needed instead of prompting;wpicker backup restoreneeds--yes.
Works with any agent that can run shell commands (Claude Code, Cursor, Copilot, and others).
Global flags: --json, --site <id|name|address>, -v.
| Command | Purpose |
|---|---|
wpicker login [address] |
Pair this computer with a site: open the link it prints and approve it in your browser (--no-browser, --device, --name) |
wpicker sites |
List your sites with live status and linked folders |
wpicker sites login|stop|start|delete|use <id> |
Re-pair, switch off/on, forget (revokes this computer), make default |
wpicker sites link [id] / unlink |
Use a site in this folder from now on / stop |
wpicker whoami |
Which site this folder uses, and why |
wpicker doctor |
Checklist: is WPicker itself set up and working |
wpicker device list / revoke <id> |
Advanced: every computer paired with the site |
wpicker context [--save] |
Site facts for you or your agent |
wpicker connect [agent…] |
Connect AI agents and other adapters (global by default; --project, --agent all, --refresh); connect list / connect remove <agent> |
wpicker guide |
Print the full agent instructions |
wpicker theme status |
To push, to pull, conflicts — and who pushed |
wpicker theme diff [path…] |
Line diff, local vs. site |
wpicker theme pull [path…] |
Apply changes from the site, merging where needed |
--all --dry-run |
Everything without asking / list only |
--theirs <path> --keep-local <path> --force |
Take the site's version / keep yours / make local match the site |
wpicker theme push |
Send local changes (--dry-run, --all) |
wpicker theme files |
List the theme's files on the site |
wpicker snapshots (alias history) |
Push history |
wpicker rollback <id> |
Restore the theme to before a snapshot |
wpicker backup list / create / download <id> / delete <id> |
Full-site backups on the site |
wpicker backup restore [id] --path <dir> |
Rebuild a backup on a local WordPress |
wpicker clone --path <dir> |
Back up the site and restore it locally |
wpicker page create --title … --builder divi|elementor|gutenberg --file … |
Create a page from a builder payload |
wpicker page update <post-id> --builder … --file … |
Update one |
wpicker map |
What is installed: plugins, themes, WooCommerce gateways, email plugins |
wpicker find <name> [--kind …] [--in …] |
Where a function, class, hook, shortcode, REST route, post type or option is defined |
wpicker logs (--since, --level, --grep, --trace, --source wc[:name]) |
The site's errors, grouped (including WooCommerce and payment gateways) |
wpicker debug on / off / status |
WordPress debug logging |
wpicker files ls / cat / grep |
Read code under wp-content |
wpicker db tables / db query "<sql>" |
Read-only database access, without credentials |
wpicker scan |
Check installed plugins against a vulnerability database |
wpicker update |
Update the CLI |
wpicker <command> --help has the details.
- Theme sync is the child theme only. Plugins, uploads and the database travel through backups, as a whole — not file by file or page by page.
- Restoring a backup from the CLI targets your computer. Restoring onto a site is done in its WP-Admin.
- Pairing needs a browser that can open the site's WP-Admin. It can be on another computer than the one running the command (type the code it shows); a machine with no browser at all cannot pair.
- Multisite networks are not supported for backups.
- Email addresses at the old domain are left as they are when a site is moved.
- Sites of many gigabytes have not been tested.
make dev # WordPress in Docker (wp-env) at http://localhost:8888, admin / password
make build-cli # bin/wpicker
make test # Go tests + PHP syntax check
make sim # end-to-end push / pull / merge scenarios against two local sites
make mirror # site-to-site backup + restore check — overwrites the target sitecontext.md describes the architecture, the REST API and how each piece works.
The CLI binaries and the plugin zip are built from this repository. You are encouraged to scan downloaded release assets with VirusTotal; new Go binaries on Windows are sometimes flagged by mistake.
Pairing uses WordPress Application Passwords, one per device and revocable; your administrator password is never stored. Approving a held push, deleting snapshots and restoring a backup onto a site can only be done by an administrator signed in to WP-Admin — not by a paired device.
All Rights Reserved.



