|
Linchpin CLI - Worktree Utils Git worktree tooling for WordPress plugin review workflows with Codex, Claude Code, Cursor, Conductor and other agents. |
|
| A Linchpin project · Actively maintained |
|
linchpin wt is a git worktree helper tuned for WordPress plugin development alongside Agent support to help easily swap Symlinks between your local environment and worktrees created by you or agents.
It is designed for this setup:
- Plugin repository in
~/Documents/GitHub/<plugin-name>. - Multiple git worktrees created by Codex or other agents.
- A shared local WordPress environment (Studio,
wp-env, or LocalWP). - A plugin directory in that environment that should point to a specific worktree via symlink.
Your plugin repo is not checked out directly into Studio, LocalWP, or wp-env on purpose. The workflow relies on symlinks so you can swap which worktree (branch) the environment sees:
- The repo lives in its own directory (e.g.
~/Documents/GitHub/my-plugin) with multiple git worktrees (e.g.main,conductor/a,feature/b). - The WordPress environment has one plugin (or theme) slot (e.g.
~/Studio/mysite/wp-content/plugins/my-plugin). That slot is a symlink pointing at one of the worktree paths. - When you run
linchpin wt switch <branch>(or pick from the list), we repoint that symlink to the chosen worktree. This allows for our local environment to use an already checked out worktree with out any errors.
So you keep a single WordPress install and switch which worktree it uses by changing the symlink target.
Use this CLI if you already like git worktree but need WordPress-specific environment switching.
This project does not replace git worktrees. It adds a WordPress workflow layer on top of them:
- Store plugin/theme target paths per local environment (
Studio,LocalWP,wp-env, custom). - Repoint one plugin/theme symlink to a different worktree with one command.
- Add safety checks around symlink replacement and worktree deletion.
- Keep one local WordPress install while reviewing many branches/worktrees.
You probably do not need this if:
- You only need
git worktree add/list/remove. - You do not use a shared local WordPress environment.
- You are fine managing symlink paths and switching manually.
| Need | Plain git worktree |
linchpin wt |
|---|---|---|
| Create/list/remove worktrees | Yes (git worktree ...) |
Yes (wrapper commands: new, ls, del, get) |
| Switch which branch your WordPress site loads | Manual symlink edits | Built-in: linchpin wt switch [branch] --env <name> |
| Save WordPress environment paths for team use | No | Yes (linchpin wt config init + .linchpin.json) |
| Guardrails for WP plugin/theme symlink targets | No | Yes (blocks non-symlink target replacement unless --force) |
| Interactive worktree picker for switching | No | Yes (TTY picker + optional fzf for cd) |
If your pain is "I can create worktrees, but switching my WordPress site between them is manual and error-prone," this tool is the fit.
npm install -g @linchpinagency/worktree-utilsFor local development in this repository:
npm linkgit2.37+ (worktree support).- Node.js
20+andnpm. - Optional:
fzffor interactivelinchpin wt cd. - A local WordPress environment (Studio,
wp-env, or LocalWP). - Your plugin repository cloned under
~/Documents/GitHub/<plugin-name>.
npm install -g @linchpinagency/worktree-utilsConfirm install:
linchpin --help
linchpin wt helpFrom the plugin or theme repo root (base worktree), run:
linchpin wt config initWhen run in an interactive terminal, you're guided through:
- Plugin or theme – Whether this repo is a WordPress plugin or theme (paths use
plugins/<slug>orthemes/<slug>). - Slug – The WordPress directory name (defaults to the repo directory name). Keep the default or type a different slug.
- Environment(s) – For each environment: Environment type (Studio, LocalWP, wp-env, or Other), which sets the base folder; then for Studio/LocalWP you pick a site from that base (list or
fzfif installed), or for wp-env you enter the WordPress root path; for Other you enter name and full path. - Choose the default environment for
linchpin wt switch.
This creates .linchpin.json. You can edit it later if paths or environments change.
- Create initial symlink(s) – If the target already exists and is not a symlink, that environment is skipped; run
linchpin wt switch --env <name> --forceto replace it.
If .linchpin.json already exists, the flow offers Overwrite, Edit (keep existing and add more environments), or Cancel.
For scripts or CI (no TTY), use non-interactive mode so a default template is written without prompts:
linchpin wt config init --plugin-slug <plugin-slug> [--force] [--no-interactive]Use --force to overwrite an existing .linchpin.json without prompting. Use --no-interactive to skip prompts even when running in a terminal.
For Studio and LocalWP, paths are built from the environment type and the site you pick:
- Studio:
~/Studio/<site>/wp-content/plugins|themes/<slug> - LocalWP:
~/Local Sites/<site>/app/public/wp-content/plugins|themes/<slug> - wp-env: You provide the WordPress root; the CLI appends
wp-content/plugins|themes/<slug>.
Use absolute paths in .linchpin.json if you edit by hand. ~ is supported.
Create a worktree for a new branch:
linchpin wt new feature/my-changeOr attach an existing remote branch:
linchpin wt get feature/existing-branchPoint your WordPress environment to that worktree:
linchpin wt switch feature/my-change --env studioCheck current worktree metadata:
linchpin wt current --link --env studioList all worktrees:
linchpin wt ls- Open or create a worktree for the branch under review.
- Run
linchpin wt switch --env <environment>to repoint the plugin symlink. - Test the branch in the shared WordPress install.
- Repeat for the next worktree/branch.
- Clean up with
linchpin wt delwhen the branch is merged.
Use command substitution for path-returning commands:
cd "$(linchpin wt cd)"
cd "$(linchpin wt home)"Missing .linchpin.json: Runlinchpin wt config initin the base worktree (interactive prompts) orlinchpin wt config init --plugin-slug <slug> --no-interactivefor a default file.Environment '<name>' is not configured: Add the environment key in.linchpin.json.Target exists and is not a symlink: Uselinchpin wt switch ... --forceonly if replacing the directory is intended.Worktree has uncommitted changeson delete: Commit/stash first, or force withlinchpin wt del --force.fzf is not installed: Installfzfor pass a branch/path directly tolinchpin wt cd <ref>.
linchpin wt ls [--json]
linchpin wt current [--link] [--env <name>]
linchpin wt switch [worktree|branch] [--env <name>] [--force] [--dry-run]
# No argument in a TTY: interactive picker from available worktrees. Non-interactive: use current worktree.
linchpin wt new [name]
linchpin wt get <branch>
linchpin wt extract
linchpin wt mv <new-branch-name>
linchpin wt del [-f|--force]
linchpin wt cd [branch|path]
linchpin wt home
linchpin wt use
linchpin wt gone
linchpin wt copy <path>
linchpin wt link <path>
linchpin wt invoke <hook>
linchpin wt config init [--plugin-slug <slug>] [--force] [--no-interactive]
linchpin wt config showShell usage notes:
linchpin wt cdandlinchpin wt homereturn paths for command substitution.- Use
cd "$(linchpin wt cd)"andcd "$(linchpin wt home)". linchpin wt cdusesfzfwhen no argument is provided.
Create .linchpin.json in the base repository root. The easiest way is to run linchpin wt config init in a terminal and follow the prompts. You can also create or edit the file manually:
{
"agent": "conductor",
"agentBasePath": "/Users/you/conductor",
"wordpress": {
"pluginSlug": "my-plugin",
"defaultEnvironment": "studio",
"environments": {
"studio": "/Users/you/Sites/studio/wp-content/plugins/my-plugin",
"wp-env": "/Users/you/Documents/projects/site/.wp-env/.../plugins/my-plugin",
"localwp": "/Users/you/Local Sites/site/app/public/wp-content/plugins/my-plugin"
}
}
}Behavior notes:
- Agent / base path:
agent(Conductor, Claude Code, Codex, or Custom Path) and optionalagentBasePathrecord where your worktree repos live. Default base paths: Conductor~/conductor, Claude Code~/Documents, Codex~/Documents/GitHub. For Custom Path you’re prompted for a base path duringconfig init. - If
defaultEnvironmentis omitted, the first environment key is used. ~is supported in configured paths.linchpin wt switchwithout a worktree argument: in an interactive terminal you get a picker of available worktrees; in non-interactive use it uses the current worktree.
Hook files are sourced in a subshell when present:
.linchpin/hooks/<hook-name>
Supported lifecycle hooks:
pre-new,post-newpre-get,post-getpre-extract,post-extractpre-mv,post-mvpre-del,post-del
Manual invocation:
linchpin wt invoke pre-newHook environment variables include LINCHPIN_BRANCH and LINCHPIN_WORKTREE.
- Open a plugin worktree.
- Run
linchpin wt switch --env studio. - Use your existing WordPress environment to review that branch.
- Move to another worktree and switch again.
- Existing symlink targets are repointed safely.
- Existing non-symlink targets are blocked unless
--forceis used. linchpin wt delblocks dirty or unmerged branches unless forced.
npm install
npm testHusky enforces Conventional Commits on commit-msg:
npm run prepareExample commit format:
feat(LINCHPIN-4850): add release automation
Releases are managed by release-please in GitHub Actions:
- Pushes to
mainrun.github/workflows/release-please.yml. release-pleaseopens/updates a release PR from conventional commits.- When the release PR is merged, a GitHub release/tag is created.
- If a release is created, the workflow publishes
@linchpinagency/worktree-utilsto npm.
