Skip to Content
CLIInstallation

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 persist

Then 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 --version

The 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/null

Install 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 commit

gtmesh 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} EOF

Safe to commit — the token comes from the environment.

Depend on the CLI

pnpm add -D @benfoster/gtmesh

pnpm 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 --check

Refresh the engine-owned files

gtmesh upgrade gtmesh types

Nothing 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@latest

Releases 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, upgrade reports it (default-available); --with-defaults writes 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.

Last updated on