Using the Graftport CLI with Claude Code: install to first publish
Set up the Graftport CLI and migration-engineer skill in Claude Code, then drive a real Shopify migration through the validate and publish loop without leaving the terminal.
Set up the Graftport CLI and migration-engineer skill in Claude Code, then drive a real Shopify migration through the validate and publish loop without leaving the terminal.
Claude Code is the cleanest fit for the Graftport CLI today: it has a first-class shell tool, it natively reads Anthropic's skill format, and the skill install command lands the migration-engineer playbook in exactly the directory Claude looks at. This guide is the install-to-first-publish walkthrough for that combination — a step short of a screenshot tour, opinionated enough that you can copy and paste it.
If you want the conceptual overview of why an AI agent suits migration mapping in the first place, read the pillar AI Shopify migration with a coding agent first. This guide assumes you have already created the migration in the Graftport app — the wizard part is unchanged.
The CLI is a Python package. The recommended install uses uv, which
keeps the tool isolated from any other Python environment on your
machine:
uv tool install graftport
Plain pip install graftport also works if you prefer. After install,
verify:
graftport --version
# graftport 0.1.0
The package is small and the install is a one-liner; uv tool uninstall graftport (or pip uninstall graftport) reverses it cleanly.
Before the PyPI release, install directly from the monorepo with an editable install so local edits are picked up without re-installing:
uv tool install --editable ./graftport
pipx install --editable ./graftport and pip install -e ./graftport
are the equivalent commands for those toolchains.
The CLI uses the same browser-based OAuth flow you would recognise from
gh auth login --web, vercel, or supabase login. The hosted
Graftport URLs are baked into the wheel, so the only thing the login
needs is your browser:
graftport auth login
Under the hood, the CLI picks a free 127.0.0.1 port, starts a
one-shot HTTP server on it, opens your browser to
<app-url>/cli-auth?state=…&port=…, and waits for the web app to POST
the resulting Supabase session back. The session is written to
~/.graftport/config.json along with the refresh token, so subsequent
CLI calls silently mint a new access token when the current one
expires. You normally only re-run auth login when the refresh token
itself expires or you explicitly auth logout.
To check the result:
graftport auth status
If you are on a server without a browser, pass --no-browser to
print the URL and open it from another machine; the CLI keeps
listening on the same loopback port.
The skill is a Markdown document that teaches a coding agent how to use the CLI: the iterative workflow, the validation error codes the validator returns, and the JSONata patterns that fix each one.
For Claude Code, one command lands it in the right place:
graftport skill install --pretty
That auto-detects which agents are present on your user account and writes the skill to the conventional location for each. For Claude Code (and Claude Desktop — Anthropic unified the on-disk skill location, so one install serves both products), the skill lands at:
~/.claude/skills/graftport-migration-engineer/
The skill is user-global, not per-project. Install it once on your machine and every Claude Code session, in every working directory, picks it up.
An earlier iteration of the same skill shipped under the name
iterating-graftport-mappings. On install for Claude, a stale
~/.claude/skills/iterating-graftport-mappings/ directory is removed
automatically — but only when its SKILL.md frontmatter confirms it
is the old graftport skill. If you happen to have an unrelated skill
sitting at that path, it is left alone.
If you want to install for only one agent, or force-overwrite a customised file:
graftport skill install --for claude # just Claude (Code + Desktop)
graftport skill install --for all # write every supported target
graftport skill install --force # overwrite a customised file
For agents the install command does not target — Cursor, GitHub
Copilot, AGENTS.md — run graftport skill > skill.md and paste the
file into whatever your agent reads. Same skill text; one extra step.
Open Claude Code in any working directory. You do not need to be inside
a Git repository — the CLI keeps its state in ~/.graftport/config.json,
not the project. A fresh Claude Code session is fine.
Tell Claude what you want, in the same tone you would use with a junior migration engineer:
Take a look at the product mapping on Graftport migration
mig_8f2aand clean up any field issues you find against a sample of 50 rows. Don't push to Shopify; I'll approve the run separately.
The skill teaches the agent to start with discovery — it runs
graftport migrations show mig_8f2a to confirm the migration exists,
graftport mappings list mig_8f2a to find the product mapping ID,
and graftport source rows mig_8f2a product --limit 5 to see real
source samples. It does not guess at the schema.
Here is the loop in commands, the way Claude Code will run them.
Pull the current mapping into a local file:
graftport mappings show <mapping_id> --raw > current.jsonata
Validate the current state against a sample:
graftport mappings validate <mapping_id> \
--jsonata current.jsonata \
--limit 50 --pretty
If validation is clean already, the agent stops and tells you. If it
fails, the response is a list of per-row errors with structured codes.
The agent edits current.jsonata to address each one, then re-runs
the validate command. This loop is the agent's bread and butter —
short, deterministic, with a clear "this passes" signal.
Publish a new version:
graftport mappings publish <mapping_id> \
--jsonata current.jsonata \
--notes "fix AMOUNT_MISMATCH on weight + status enum"
mappings publish is agent-allowed: no --yes is required because the
write is metadata-only. It bumps the mapping version and flags downstream
records for re-load on the next run. The next run is itself gated, so
publishing freely is safe.
Dry-run end-to-end:
graftport runs start <migration_id> --dry-run
Also agent-allowed. The dry-run computes every payload Shopify would receive without pushing it. Zero load cost. You inspect the result in the Data tab in the app and decide whether to ship a real run.
For the mapping-side mechanics in more depth — which JSONata patterns solve which error codes, when to split mappings across multiple versions — read Automating JSONata mappings with AI.
When the dry-run looks right, the agent will compose the real run command and stop. In a Claude Code session it looks like this:
graftport runs start mig_8f2a
The CLI prints the resolved action and a live cost estimate to
stderr, then blocks waiting for an interactive yes. The bundled
skill is explicit that the agent must not pass --yes to bypass the
gate. You read what is about to happen, type yes, and the run
starts. If you type anything else, the command exits with code 3 —
distinct from a failure (1) — so the agent and your wrapping scripts
can tell "you declined" apart from "something broke."
For the full design rationale behind the two-tier model, see Human approval gates for AI Shopify migrations.
The same CLI runs in CI without a browser. If you already have a Supabase JWT (a service account token from your CI secrets manager, for example), pass it directly on login and skip the browser flow:
graftport auth login \
--postgrest-url https://<your-project>.supabase.co/rest/v1 \
--apikey "$YOUR_PUBLIC_ANON_KEY" \
--validate-url https://<your-fastapi-deploy>.example.com \
--access-token "$YOUR_JWT"
Configs written this way carry no refresh token, so you re-run
auth login when the JWT expires. CI is also the one place where it
is reasonable for a human operator to script --yes onto gated
commands — but only when a human wrote the script and approved the
batch up front. The agent itself still does not pass it.
If you are running a local Graftport stack — Supabase on :54321,
FastAPI on :8000, Next.js on :3000 — point the login at the dev
URLs explicitly:
graftport auth login \
--postgrest-url http://127.0.0.1:54321/rest/v1 \
--apikey "$NEXT_PUBLIC_SUPABASE_ANON_KEY" \
--validate-url http://127.0.0.1:8000 \
--app-url http://127.0.0.1:3000
The browser flow opens <app-url>/cli-auth, waits for sign-in, and
writes the session to ~/.graftport/config.json. The Supabase JWT
carries the app.tenant_id claim so RLS scopes everything to your
dev tenant.
The same flags accept production overrides if you self-host Graftport,
and the corresponding GRAFTPORT_DEFAULT_* environment variables work
for any of them.
After these eight steps you have:
The conceptual overview lives at /ai-migrations — the install commands, the agent matrix, and the FAQ in one place.
Once your Claude Code session is producing clean validates and green dry-runs, the rest of the migration is the same loop on the other resource types. Start one at app.graftport.com/signup or read the /ai-migrations overview.
Connect a source store, dry-run a migration, see the exact Shopify result before a single record lands. The same platform your team will use on go-live night.