Installation
gtmesh publishes to GitHub Packages as @benfoster/gtmesh, a private package. Access comes from
the repository — once you have read on benfoster/GTMesh, a token of yours can pull it. Ask Ben
for that first; everything below assumes it.
Get access
GitHub Packages authenticates every read, so pnpm needs a token. If you already use the GitHub CLI , you have one — it just needs the packages scope:
gh auth refresh -h github.com -s read:packages # one-time
export GTMESH_TOKEN=$(gh auth token) # add to ~/.zshrc to persistThen point the @benfoster scope at GitHub Packages. Put it in ~/.npmrc — the pnpm dlx
bootstrap below runs before any project exists, so the machine-wide file is what resolves it (a
scaffolded mesh then also carries its own committed .npmrc, so the repo works for anyone whose
token is in the environment):
@benfoster:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GTMESH_TOKEN}Referencing the variable keeps the token out of the file — safe to commit a project .npmrc.
No GitHub CLI? Create a classic personal access token with
the read:packages scope and export it as GTMESH_TOKEN instead. The gh route is worth preferring
because the token follows your login — there’s nothing to rotate by hand.
Install gtmesh
gtmesh installs per project: every mesh repo carries @benfoster/gtmesh as a devDependency and
runs it as pnpm exec gtmesh …. Each mesh pins its own engine version, so two meshes on different
versions never conflict — and upgrading one never surprises another.
Scaffolding is the one moment there’s no project yet — pnpm dlx runs the CLI one-shot from the
registry without installing anything:
pnpm dlx @benfoster/gtmesh init my-mesh # or: init --template acme for a worked example
cd my-mesh
pnpm add -D @benfoster/gtmesh # the mesh pins its own engine version
pnpm exec gtmesh --versionThe binary is still gtmesh; only the package name is scoped. Inside the repo, pnpm exec gtmesh
(or a "gtmesh" package script) always resolves the pinned version. Then open the mesh in Claude
Code, run the operator skill, and follow the walkthrough. The full
command list is in the Reference.
If an older machine-wide copy lingers from before the registry existed, remove it so it can never shadow a mesh’s pinned version:
pnpm rm -g gtmesh 2>/dev/null; pnpm rm -g @benfoster/gtmesh 2>/dev/nullInstall straight from git
Installing over SSH skips the registry and its token entirely, using the key you already push with — same per-project shape, a git ref instead of a semver:
pnpm add -D "git+ssh://git@github.com/benfoster/GTMesh.git#v1.3.0" # a released tag
pnpm add -D "git+ssh://git@github.com/benfoster/GTMesh.git#a1b2c3d" # any commitgtmesh builds itself on install, so this yields a working CLI. Prefer the registry for everyday
use: it accepts semver ranges (^1.3.0, which a git pin can’t express) and installs a prebuilt
tarball rather than compiling from source on every install.
Provider access (Ahrefs)
gtmesh runs entirely offline except the pull channels, which
need an Ahrefs API token in the environment. Never commit it:
export AHREFS_API_TOKEN=…Everything else — plan, apply, the lifecycle, types — works with no network and no token.
Install in CI
CI needs a token of its own. A workflow in a mesh repo runs with a GITHUB_TOKEN scoped to that
repo, which can’t read a package published from benfoster/GTMesh — and there’s no interactive gh
login to borrow. Store a classic PAT with read:packages as a repo secret (e.g. GTMESH_TOKEN) and
hand it to the install step:
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://npm.pkg.github.com'
scope: '@benfoster'
- run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GTMESH_TOKEN }}A mesh scaffolded by a recent gtmesh types already has this workflow, the .npmrc and the
devDependency. An older one doesn’t — see below.
Move an existing mesh onto the published CLI
A mesh built before the CLI was published leaned on a machine-wide copy or a source checkout. Three
files wire it to the registry instead, and gtmesh types creates them only when absent, so an
existing repo keeps whatever it has and you add them by hand. Run this from the mesh repo:
Point the scope at the registry
cat > .npmrc <<'EOF'
@benfoster:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GTMESH_TOKEN}
EOFSafe to commit — the token comes from the environment.
Depend on the CLI
pnpm add -D @benfoster/gtmeshpnpm exec gtmesh … now resolves inside the repo, so the mesh pins its own engine version rather
than depending on whatever is installed globally.
Give CI the token
Add a classic PAT with read:packages as the GTMESH_TOKEN repo secret, then update
.github/workflows/content-types.yml — the types --check gate — to authenticate its install:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
registry-url: https://npm.pkg.github.com # add
scope: "@benfoster" # add
- run: pnpm install --frozen-lockfile
env: # add
NODE_AUTH_TOKEN: ${{ secrets.GTMESH_TOKEN }}
- run: pnpm exec gtmesh types --checkRefresh the engine-owned files
gtmesh upgrade
gtmesh typesNothing here touches your content, graph or config — it changes only how the engine is obtained.
Finish by removing any machine-wide copy (pnpm rm -g gtmesh) so every invocation resolves the
version this mesh pins.
Upgrade the CLI
Bump a mesh’s pinned gtmesh to the latest release — per project, so each mesh moves when you decide:
pnpm add -D @benfoster/gtmesh@latestReleases are cut from conventional-commit history: feat bumps the minor, fix the patch, and a
breaking change the major — meaning a major is exactly the case where gtmesh upgrade alone won’t
carry a mesh forward. The changelog
records what each release needs.
Upgrade a project (gtmesh upgrade)
Separate from upgrading the binary: once a newer gtmesh ships improved engine-owned scaffold
files, gtmesh upgrade re-applies them into an existing mesh repo — without clobbering your edits
or your project files.
gtmesh upgrade --dry-run # preview: added / refreshed / *.new / default-available / skipped
gtmesh upgrade # apply it
gtmesh upgrade --with-defaults # also fetch changed project-file defaults as *.default to hand-merge- Engine-owned files (the docs, the shared schema base
schemas/common.schema.yaml, the capability skills) are refreshed if you haven’t touched them; if you have, the new version lands beside yours as<file>.new— your edit is never overwritten. - Project-owned files (your config, reference data, per-type schemas, templates, foundation)
are never overwritten. If the engine’s default for one improves,
upgradereports it (default-available);--with-defaultswrites that new default as<name>.default.<ext>so you can merge selectively. - New files are added; files removed from the bundle are reported, not deleted. Idempotent — a second run with no changes does nothing.
Two different “upgrades”: Upgrade the CLI bumps the gtmesh binary; gtmesh upgrade refreshes
the scaffolded files inside one mesh repo. You’ll usually do the first, then run the second in each
project.