{"_id":"@linchpinagency/cli","name":"@linchpinagency/cli","dist-tags":{"latest":"1.1.1"},"versions":{"1.1.1":{"name":"@linchpinagency/cli","version":"1.1.1","repository":{"type":"git","url":"git+https://github.com/linchpin/cli.git"},"description":"Linchpin's command line tool for WordPress and agent workflows — git worktree management, local environment setup, and deterministic verbs agents can call without approval prompts","license":"GPL-2.0-only","type":"module","bin":{"linchpin":"dist/cli.js"},"scripts":{"build":"tsdown","typecheck":"tsc --noEmit","pretest":"npm run build","test":"node --test","prepack":"npm run build","prepare":"husky || true","commitlint":"commitlint --edit"},"devDependencies":{"@commitlint/cli":"^21.0.0","@commitlint/config-conventional":"^21.0.0","@inquirer/prompts":"^8.2.1","@types/node":"^24.0.0","@vercel/detect-agent":"^1.2.4","commander":"^15.0.0","husky":"^9.1.7","picocolors":"^1.1.1","std-env":"^4.2.0","tinyexec":"^1.3.0","tsdown":"^0.22.14","typescript":"^7.0.2","unrun":"^0.3.1","zod":"^4.4.3"},"engines":{"node":">=22.12.0"},"keywords":["git","worktree","wordpress","linchpin"],"gitHead":"b48036c76f05db86ee57c9ea626a781c1823d26a","_id":"@linchpinagency/cli@1.1.1","bugs":{"url":"https://github.com/linchpin/cli/issues"},"homepage":"https://github.com/linchpin/cli#readme","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-2wQvlHuRooISO5/OdVZZ4kHEtVrpjCfwbgenDakH+vktKKvBnOQhKn0SyRR6L4PvBSWHa1WbYHjK2LuxCf/MWw==","shasum":"5d14f4b155c3f4905ab6c928a28c013f15ceb0dd","tarball":"https://registry.npmjs.org/@linchpinagency/cli/-/cli-1.1.1.tgz","fileCount":9,"unpackedSize":477152,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDiBlxzDbXB307PHQo2FgXaMmgWG4JkyMtcaFFHXLwrcwIgEqr6MIdurEgG650EEiP37rmOt/zKcnQvIdlATMSLXUA="}]},"_npmUser":{"name":"aaronware","email":"aaron@linchpin.com"},"directories":{},"maintainers":[{"name":"aaronware","email":"aaron@linchpin.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cli_1.1.1_1786224014573_0.3841897818154558"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-08T21:20:14.384Z","1.1.1":"2026-08-08T21:20:14.731Z","modified":"2026-08-08T21:20:14.945Z"},"maintainers":[{"name":"aaronware","email":"aaron@linchpin.com"}],"description":"Linchpin's command line tool for WordPress and agent workflows — git worktree management, local environment setup, and deterministic verbs agents can call without approval prompts","homepage":"https://github.com/linchpin/cli#readme","keywords":["git","worktree","wordpress","linchpin"],"repository":{"type":"git","url":"git+https://github.com/linchpin/cli.git"},"bugs":{"url":"https://github.com/linchpin/cli/issues"},"license":"GPL-2.0-only","readme":"<table width=\"100%\">\n  <tr>\n    <td align=\"left\" width=\"70%\">\n      <strong>Linchpin CLI</strong><br />\n      Git worktree tooling for WordPress plugin review workflows with Codex, Claude Code, Cursor, Conductor and other agents.\n    </td>\n    <td align=\"center\" width=\"30%\">\n      <img src=\"https://badge.fury.io/js/@linchpinagency%2Fcli.svg\" alt=\"npm version\" />\n      <img src=\"https://img.shields.io/github/license/linchpin/cli\" alt=\"License\" />\n      <img src=\"https://img.shields.io/badge/Node-%3E%3D22.12-339933?logo=node.js&logoColor=white\" alt=\"Node >= 22.12\" />\n      <br />\n      <img src=\"https://img.shields.io/github/actions/workflow/status/linchpin/cli/ci.yml?label=CI\" alt=\"CI status\" />\n      <img src=\"https://img.shields.io/github/actions/workflow/status/linchpin/cli/release-please.yml?label=release\" alt=\"Release status\" />\n      <br />\n      <img src=\"https://img.shields.io/github/last-commit/linchpin/cli\" alt=\"Last commit\" />\n      <img src=\"https://img.shields.io/badge/PRs-welcome-brightgreen\" alt=\"PRs welcome\" />\n      <img src=\"https://img.shields.io/badge/WordPress-%2321759B?logo=wordpress&logoColor=white\" alt=\"WordPress\" />\n    </td>\n  </tr>\n  <tr>\n    <td>\n      A <strong><a href=\"https://linchpin.com\">Linchpin</a></strong> project · <em>Actively maintained</em>\n    </td>\n    <td align=\"center\" width=\"30%\">\n      <img src=\"https://assets.linchpin.com/linchpin-logo-primary.svg\" width=\"100\" alt=\"Linchpin\" />\n    </td>\n  </tr>\n</table>\n\n## What is this CLI?\n\n`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.\n\nIt is designed for this setup:\n\n- Plugin repository in `~/Documents/GitHub/<plugin-name>`.\n- Multiple git worktrees created by Codex or other agents.\n- A shared local WordPress environment (Studio, `wp-env`, or LocalWP).\n- A plugin directory in that environment that should point to a specific worktree via symlink.\n\n### Why symlinks? Why isn’t the repo checked out directly in my environment?\n\nYour 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:\n\n- The repo lives in its own directory (e.g. `~/Documents/GitHub/my-plugin`) with multiple [git worktrees](https://git-scm.com/docs/git-worktree) (e.g. `main`, `conductor/a`, `feature/b`).\n- 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.\n- 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. \n\nSo you keep a single WordPress install and switch which worktree it uses by changing the symlink target.\n\n## Should you use this?\n\nUse this CLI if you already like `git worktree` but need WordPress-specific environment switching.\n\nThis project does **not** replace git worktrees. It adds a WordPress workflow layer on top of them:\n\n- Store plugin/theme target paths per local environment (`Studio`, `LocalWP`, `wp-env`, custom).\n- Repoint one plugin/theme symlink to a different worktree with one command.\n- Add safety checks around symlink replacement and worktree deletion.\n- Keep one local WordPress install while reviewing many branches/worktrees.\n\nYou probably **do not** need this if:\n\n- You only need `git worktree add/list/remove`.\n- You do not use a shared local WordPress environment.\n- You are fine managing symlink paths and switching manually.\n\n## `linchpin wt` vs plain `git worktree`\n\n| Need | Plain `git worktree` | `linchpin wt` |\n|---|---|---|\n| Create/list/remove worktrees | Yes (`git worktree ...`) | Yes (wrapper commands: `new`, `ls`, `del`, `get`) |\n| Switch which branch your WordPress site loads | Manual symlink edits | Built-in: `linchpin wt switch [branch] --env <name>` |\n| Save WordPress environment paths for team use | No | Yes (`linchpin wt config init` + `.linchpin.json`) |\n| Guardrails for WP plugin/theme symlink targets | No | Yes (blocks non-symlink target replacement unless `--force`) |\n| Interactive worktree picker for switching | No | Yes (TTY picker + optional `fzf` for `cd`) |\n\nIf your pain is \"I can create worktrees, but switching my WordPress site between them is manual and error-prone,\" this tool is the fit.\n\n## Install\n\n```bash\nnpm install -g @linchpinagency/cli\n```\n\nFor local development in this repository:\n\n```bash\nnpm link\n```\n\n## Team setup guide\n\n### 1. Prerequisites\n\n- `git` 2.37+ (worktree support).\n- Node.js `22.12+` and `npm`.\n- Optional: `fzf` for interactive `linchpin wt cd`.\n- A local WordPress environment (Studio, `wp-env`, or LocalWP).\n- Your plugin repository cloned under `~/Documents/GitHub/<plugin-name>`.\n\n### 2. Install CLI\n\n```bash\nnpm install -g @linchpinagency/cli\n```\n\nConfirm install:\n\n```bash\nlinchpin --help\nlinchpin wt help\n```\n\n### 3. Initialize project config\n\nFrom the plugin or theme repo root (base worktree), run:\n\n```bash\nlinchpin wt config init\n```\n\nWhen run in an interactive terminal, you're guided through:\n\n1. **Agents** – Which agent base path(s) you use (Conductor, Claude Code, Codex, and/or Custom Path). You can select **multiple agents** so worktrees are found whether you're under Codex, Conductor, or another path — this avoids detached-HEAD issues when switching between agents. If you pick more than one, you choose a **default agent** for new worktrees.\n2. **Plugin, theme, or wp-content** – Whether this repo is a WordPress plugin, theme, or a full wp-content project (the entire wp-content folder is the repo). You can pre-select this with `--type <plugin|theme|wp-content>`.\n3. **Slug / symlink name** – For plugins and themes, the WordPress directory name (defaults to the repo directory name). For wp-content projects, the symlink name (defaults to `wp-content`) — useful when your repo has a client name instead of `wp-content`.\n4. **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 `fzf` if installed), or for wp-env you enter the WordPress root path; for Other you enter name and full path.\n5. Choose the **default environment** for `linchpin wt switch`.\n\nThis creates `.linchpin.json`. You can edit it later if paths or environments change.\n\n6. **Create initial symlink(s)** – If the target already exists as a real folder (not a symlink), you're prompted to **back it up** (rename with `.bkp` suffix), **delete** it (with confirmation), or **skip** that environment.\n\nIf `.linchpin.json` already exists, the flow offers **Overwrite**, **Edit** (keep existing and add more environments), or **Cancel**.\n\nFor scripts or CI (no TTY), use non-interactive mode so a default template is written without prompts:\n\n```bash\nlinchpin wt config init --type <plugin|theme|wp-content> [--plugin-slug <slug>] [--force] [--no-interactive]\n```\n\nUse `--type wp-content` when your repo represents an entire wp-content folder (common for client projects where the repo is named after the client, not `wp-content`). Use `--force` to overwrite an existing `.linchpin.json` without prompting. Use `--no-interactive` to skip prompts even when running in a terminal.\n\n### 4. Paths built by config init\n\nFor Studio and LocalWP, paths are built from the environment type and the site you pick:\n\n- **Studio**: `~/Studio/<site>/wp-content/plugins|themes/<slug>` (or `~/Studio/<site>/wp-content` for wp-content projects)\n- **LocalWP**: `~/Local Sites/<site>/app/public/wp-content/plugins|themes/<slug>` (or `…/wp-content` for wp-content projects)\n- **wp-env**: You provide the WordPress root; the CLI appends `wp-content/plugins|themes/<slug>` (or `wp-content` for wp-content projects).\n\nUse absolute paths in `.linchpin.json` if you edit by hand. `~` is supported.\n\n### 5. Create and switch worktrees\n\nCreate a worktree for a new branch:\n\n```bash\nlinchpin wt new feature/my-change\n```\n\nOr attach an existing remote branch:\n\n```bash\nlinchpin wt get feature/existing-branch\n```\n\nPoint your WordPress environment to that worktree:\n\n```bash\nlinchpin wt switch feature/my-change --env studio\n```\n\n### 6. Verify active target\n\nCheck current worktree metadata:\n\n```bash\nlinchpin wt current --link --env studio\n```\n\nList all worktrees:\n\n```bash\nlinchpin wt ls\n```\n\n### 7. Daily review workflow\n\n1. Open or create a worktree for the branch under review.\n2. Run `linchpin wt switch --env <environment>` to repoint the plugin symlink.\n3. Test the branch in the shared WordPress install.\n4. Repeat for the next worktree/branch.\n5. Clean up with `linchpin wt del` when the branch is merged.\n\n### 8. Switch and cd in one step\n\nAfter `wt switch` repoints a symlink your shell is still in the **old** worktree. Wrap the command in `cd` to land in the new target automatically:\n\n```bash\ncd \"$(linchpin wt switch feature/my-change)\"\ncd \"$(linchpin wt switch)\"                     # interactive picker\ncd \"$(linchpin wt switch --env localwp)\"       # specific environment\n```\n\nWhen piped (wrapped in `$()`), informational output goes to stderr so you still see it, while stdout carries the symlink path for `cd`.\n\n**Optional: fully automatic with shell-init**\n\nIf you prefer `linchpin wt switch` to handle the `cd` for you every time, add this to your shell profile (`~/.zshrc`, `~/.bashrc`, or `~/.config/fish/config.fish`):\n\n```bash\neval \"$(linchpin shell-init)\"\n```\n\nThis defines a thin wrapper that re-enters your current directory after a successful switch, so the shell picks up the repointed symlink. The shell is auto-detected from `$SHELL`. To force a specific shell: `eval \"$(linchpin shell-init --shell zsh)\"`.\n\n### 9. Path helpers\n\nUse command substitution for other path-returning commands:\n\n```bash\ncd \"$(linchpin wt cd)\"\ncd \"$(linchpin wt home)\"\n```\n\n### 10. Troubleshooting\n\n- `Missing .linchpin.json`:\n  Run `linchpin wt config init` in the base worktree (interactive prompts) or `linchpin wt config init --type plugin --plugin-slug <slug> --no-interactive` for a default file.\n- `Environment '<name>' is not configured`:\n  Add the environment key in `.linchpin.json`.\n- `Target exists and is not a symlink`:\n  Use `linchpin wt switch ... --force` only if replacing the directory is intended.\n- `Worktree has uncommitted changes` on delete:\n  Commit/stash first, or force with `linchpin wt del --force`.\n- `fzf is not installed`:\n  Install `fzf` or pass a branch/path directly to `linchpin wt cd <ref>`.\n\n## Command surface\n\n```bash\nlinchpin shell-init [--shell bash|zsh|fish]\n\nlinchpin wt ls [--json]\nlinchpin wt current [--link] [--env <name>]\nlinchpin wt switch [worktree|branch] [--env <name>] [--force] [--dry-run]\n  # No argument in a TTY: interactive picker from available worktrees. Non-interactive: use current worktree.\n  # When piped, outputs the symlink target path for cd: cd \"$(linchpin wt switch ...)\"\n\nlinchpin wt new [name]\nlinchpin wt get <branch>\nlinchpin wt extract\nlinchpin wt mv <new-branch-name>\nlinchpin wt del [-f|--force]\nlinchpin wt cd [branch|path]\nlinchpin wt home\nlinchpin wt use\nlinchpin wt gone\nlinchpin wt copy <path>\nlinchpin wt link <path>\nlinchpin wt invoke <hook>\n\nlinchpin wt config init [--type <plugin|theme|wp-content>] [--plugin-slug <slug>] [--force] [--no-interactive]\nlinchpin wt config show\n```\n\nShell usage notes:\n\n- `linchpin wt cd` and `linchpin wt home` return paths for command substitution.\n- Use `cd \"$(linchpin wt cd)\"` and `cd \"$(linchpin wt home)\"`.\n- `linchpin wt cd` uses `fzf` when no argument is provided.\n\n## Configuration\n\nCreate `.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:\n\nPlugin/theme project:\n\n```json\n{\n  \"agent\": \"conductor\",\n  \"agentBasePath\": \"/Users/you/conductor\",\n  \"wordpress\": {\n    \"contentType\": \"plugin\",\n    \"pluginSlug\": \"my-plugin\",\n    \"defaultEnvironment\": \"studio\",\n    \"environments\": {\n      \"studio\": \"/Users/you/Sites/studio/wp-content/plugins/my-plugin\",\n      \"wp-env\": \"/Users/you/Documents/projects/site/.wp-env/.../plugins/my-plugin\",\n      \"localwp\": \"/Users/you/Local Sites/site/app/public/wp-content/plugins/my-plugin\"\n    }\n  }\n}\n```\n\nWP-content project (repo is the entire wp-content folder):\n\n```json\n{\n  \"agent\": \"conductor\",\n  \"wordpress\": {\n    \"contentType\": \"wp-content\",\n    \"defaultEnvironment\": \"localwp\",\n    \"environments\": {\n      \"localwp\": \"/Users/you/Local Sites/site/app/public/wp-content\"\n    }\n  }\n}\n```\n\nMulti-agent (Codex and Conductor, etc.) — we look for worktrees in all listed paths:\n\n```json\n{\n  \"agents\": {\n    \"codex\": \"~/Documents/GitHub\",\n    \"conductor\": \"~/conductor\"\n  },\n  \"defaultAgent\": \"codex\",\n  \"wordpress\": {\n    \"contentType\": \"plugin\",\n    \"pluginSlug\": \"my-plugin\",\n    \"defaultEnvironment\": \"studio\",\n    \"environments\": {\n      \"studio\": \"/Users/you/Sites/studio/wp-content/plugins/my-plugin\"\n    }\n  }\n}\n```\n\nIf the repo uses a custom symlink name (e.g. the repo is named after the client), add `\"symlinkName\"`:\n\n```json\n{\n  \"wordpress\": {\n    \"contentType\": \"wp-content\",\n    \"symlinkName\": \"client-wp-content\",\n    \"defaultEnvironment\": \"localwp\",\n    \"environments\": {\n      \"localwp\": \"/Users/you/Local Sites/site/app/public/client-wp-content\"\n    }\n  }\n}\n```\n\nBehavior notes:\n\n- **Agent / base path**: You can use a single agent or **multiple agents**. Single-agent config uses `agent` (Conductor, Claude Code, Codex, or Custom Path) and optional `agentBasePath`. Multi-agent config uses `agents` (object of name → base path) and optional `defaultAgent`. Default base paths: Conductor `~/conductor`, Claude Code `~/Documents`, Codex `~/Documents/GitHub`. For Custom Path you’re prompted for a base path during `config init`. When you use multiple agents (e.g. Codex for some work and Conductor for another), we look for worktrees in all configured paths so the correct main repo is found and detached-HEAD issues are avoided.\n- If `defaultEnvironment` is omitted, the first environment key is used.\n- `~` is supported in configured paths.\n- `linchpin wt switch` without a worktree argument: in an interactive terminal you get a **picker** of available worktrees; in non-interactive use it uses the current worktree.\n\n## Hooks\n\nHook files are sourced in a subshell when present:\n\n- `.linchpin/hooks/<hook-name>`\n\nSupported lifecycle hooks:\n\n- `pre-switch`, `post-switch`\n- `pre-new`, `post-new`\n- `pre-get`, `post-get`\n- `pre-extract`, `post-extract`\n- `pre-mv`, `post-mv`\n- `pre-del`, `post-del`\n\nManual invocation:\n\n```bash\nlinchpin wt invoke pre-new\nlinchpin wt invoke post-switch\n```\n\nHook environment variables include `LINCHPIN_BRANCH`, `LINCHPIN_WORKTREE`, and for switch hooks `LINCHPIN_ENVIRONMENT`.\n\nTo run commands after switching worktrees (e.g. `composer install`, `npm run build`), create `.linchpin/hooks/post-switch`. The hook runs with the worktree as the current directory:\n\n```bash\n#!/bin/bash\ncomposer install\nnpm install && npm run build\n```\n\n## Typical WordPress review flow\n\n1. Open a plugin worktree.\n2. Run `cd \"$(linchpin wt switch --env studio)\"` to repoint the symlink and land in the new target.\n3. Use your existing WordPress environment to review that branch.\n4. Move to another worktree and switch again.\n\n## Safety behavior\n\n- Existing symlink targets are repointed safely.\n- Existing non-symlink targets are blocked unless `--force` is used.\n- `linchpin wt del` blocks dirty or unmerged branches unless forced.\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck   # tsc --noEmit\nnpm run build       # tsdown -> dist/\nnpm test            # builds first, then node --test\n```\n\nThe CLI is TypeScript and ESM, built with [tsdown](https://tsdown.dev). Every runtime\ndependency lives in `devDependencies` and is bundled into `dist/`, so the published package\ninstalls with **zero transitive dependencies**. Un-ported CommonJS still lives in `legacy/`,\nwhich carries its own `package.json` declaring `\"type\": \"commonjs\"`; it is being drained into\n`src/` command by command.\n\nHusky enforces Conventional Commits on `commit-msg`:\n\n```bash\nnpm run prepare\n```\n\nExample commit format:\n\n```text\nfeat(LINCHPIN-4850): add release automation\n```\n\n### Continuous integration\n\n`.github/workflows/ci.yml` runs on every pull request and on pushes to `main`: typecheck,\nbuild and tests across Node **22.12** (the `engines` floor) and **24**.\n\nIt also gates on **agent-readiness** using\n[`cli-agent-lint`](https://github.com/Camil-H/cli-agent-lint), which grades a CLI A–F across\n34 checks covering flow safety, token efficiency, self-description, automation safety and\npredictability.\n\nCI fails if the score drops below a recorded floor, and the floor rises whenever the score\ndoes, so a gain can't be given back silently.\n\n| Recorded | Score | Where | What moved |\n| --- | --- | --- | --- |\n| 2026-08-06 | 77.2% (B) | local | Baseline, pre-rewrite surface |\n| 2026-08-06 | 80.7% (B) | local | Command registry — usage examples in help, actionable errors, control characters rejected in argv |\n| 2026-08-06 | **84.7% (B)** | **CI** | Dual-mode contract — `--json`, `--quiet`, `--no-color`, documented exit codes |\n\n⚠️ **Record the number CI reports, not a local run.** SD-5 (skill / context files) passes on a\nworkstation off an untracked, gitignored `.claude/` directory that doesn't exist in a clean\ncheckout, so local runs read roughly 1.7 points high. The first two rows above were measured\nlocally and are inflated for that reason; CI is the gate, so CI is the measurement.\n\nStill outstanding: shell completions and schema introspection (SD-3/SD-4), env-var auth\n(FS-4, arrives with `linchpin task`), skill/context files (SD-5, arrives with the bundled\nskills), and a `--timeout` flag (PV-1).\n\nOne check stays a warning **on purpose**. SD-1 wants errors to be JSON on stderr by default;\nthis CLI is human-readable by default and structured only when asked (`--json`), matching\n`gh` and `wrangler`. In `--json` mode stdout carries exactly one envelope and stderr stays\nempty, including on failure.\n\n## Output modes and exit codes\n\nMode is decided once at startup: an explicit `--json` / `--plain` / `--quiet` flag, then\n`LINCHPIN_OUTPUT`, then whether stdout is a TTY. Warnings always go to stderr so stdout stays\nparseable.\n\n⚠️ **`CI` is unset inside Claude Code while no stream is a TTY.** Anything that gates\nprompting on a CI check alone classifies an agent as interactive and blocks forever. The\nnon-TTY check is the safety net.\n\n| Code | Meaning |\n| --- | --- |\n| 0 | Success |\n| 1 | Unexpected error |\n| 2 | Validation or usage error |\n| 3 | Precondition not met |\n| 4 | Authentication required or rejected |\n| 5 | Refused by a safety check |\n\n## Releases\n\nReleases are managed by `release-please` in GitHub Actions:\n\n- Pushes to `main` run `.github/workflows/release-please.yml`.\n- `release-please` opens/updates a release PR from conventional commits.\n- When the release PR is merged, a GitHub release/tag is created.\n- If a release is created, the workflow publishes `@linchpinagency/cli` to npm.\n\n![Linchpin an award winning digital agency building immersive, high performing web experiences](https://assets.linchpin.com/github/linchpin-github-repo-banner.jpg)\n","readmeFilename":"README.md","_rev":"1-38964e26371d3a94b925a93d09c57870"}