Skip to main content

CLI for AI-assisted JSONata mapping iteration against Graftport

Project description

graftport

CLI for AI-assisted Graftport migration engineering. Designed to be driven by a coding agent (Claude Code, Cursor, Windsurf, Copilot Workspace, …) over its shell tool — works anywhere a shell does, no MCP support required.

The user creates the migration (project, credentials, resource scope) in the Graftport UI; the agent then acts as a migration engineer: investigate source data, iterate and publish mappings, run dry-runs to evaluate their output, triage load failures, and judge whether the migration is ready. Cost-incurring actions (real loads, cancels, retries) are composed and presented for human approval — never executed autonomously.

Install

uv tool install graftport          # recommended (once published)
# or
pip install graftport

Installing from this repo (before the PyPI release)

The CLI hasn't been published yet, so for now install it directly from the monorepo. Run from the repo root:

# Editable install — picks up local edits without re-installing.
uv tool install --editable ./graftport

# Or with plain pip / pipx:
pipx install --editable ./graftport
# or
pip install -e ./graftport

After install, graftport --version should print graftport 0.1.0. uv tool uninstall graftport (or pipx uninstall graftport) reverses the install cleanly.

If you'd rather not install at all, you can run the CLI from a checkout via uv:

cd graftport
uv run graftport --help

Pointing at a local Graftport stack

graftport auth login defaults to the production URLs. For local dev pass the dev URLs explicitly. With a local Supabase, the FastAPI on :8000, and the Next.js app on :3000:

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 you to sign in, and writes the Supabase session (including the refresh token) to ~/.graftport/config.json. The Supabase JWT carries the app.tenant_id claim, so RLS scopes everything to your dev tenant. To skip the browser flow on a fully headless setup, pass --access-token "<jwt>" directly.

Quick start

The hosted Graftport project URL and the public anon key are baked into the wheel, so the only thing auth login needs is your browser:

graftport auth login               # opens the browser to sign in
graftport mappings list <migration_id>
graftport mappings show <mapping_id> --raw > current.jsonata
graftport source rows <migration_id> product --limit 5 > samples.json
# edit current.jsonata
graftport mappings validate <mapping_id> --jsonata current.jsonata --limit 50 --pretty
graftport mappings publish <mapping_id> --jsonata current.jsonata --notes "fix AMOUNT_MISMATCH"

Command tree

Group Commands State
auth login / status / logout local config
migrations list / show / resources read-only
mappings list / show / validate read-only
mappings publish agent-allowed
runs list / show / status (--watch) / estimate read-only
runs start --dry-run agent-allowed
runs start (real) / cancel human-gated
records failures / errors / show / loaded read-only
records retry human-gated
source rows / raw read-only
skill (bare) / install local files

Every command emits JSON on stdout by default and accepts --pretty. Exit codes: 0 ok, 1 error, 2 usage, 3 a human-gated action was declined (or validation failed).

Human-approval contract

State-changing actions split into two tiers:

Agent-allowed (no human gate, no --yes required):

  • mappings publish — metadata write that flags downstream records for re-load on the next run, which is itself gated.
  • runs start --dry-run — computes payloads without pushing to Shopify; zero load cost.

Human-gated (interactive yes required; --yes bypass for a human operator scripting the CLI):

  • runs start without --dry-run — costs Shopify API + platform credits per loaded record.
  • runs cancel — destructive; may lose in-flight work.
  • records retry — re-loads one record; costs.

The gated commands print the resolved action (and, for start, a live cost estimate) to stderr and then block. The bundled skill forbids the agent from ever passing --yes on a gated command — the agent composes the command, presents it, and stops. A declined gate exits 3, distinct from a command failure (1).

The CLI consumes only the existing PostgREST views/RPCs and the FastAPI /api/validate endpoint — no new backend endpoints or schema.

The agent skill document

Every install of graftport ships with a Markdown skill document describing the iterative workflow, the validation error codes, and JSONata patterns that fix each one.

The fastest way to get it in front of your coding agent is to install it directly. The skill teaches an agent how to use the CLI in general, so it always installs user-globally — once per machine, not per project:

graftport skill install --pretty

That auto-detects which coding agents are installed for your user and writes the right file for each. Supported targets:

Agent Where the skill lands Notes
Claude Code and Claude Desktop ~/.claude/skills/graftport-migration-engineer/ One install serves both products — Anthropic unified the on-disk location.
Windsurf ~/.codeium/windsurf/memories/global_rules.md Idempotent block-merge into your global rules file.

This skill supersedes the earlier iterating-graftport-mappings skill. 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. An unrelated skill that happens to sit at that path is left untouched.

Targeted install:

graftport skill install --for claude         # just one agent
graftport skill install --for all            # write everything
graftport skill install --force              # overwrite a customised file

Cursor, Copilot Workspace, and AGENTS.md are not supported by install — Cursor's User Rules live only in the Settings UI, Copilot reads .github/copilot-instructions.md per-repo only, and AGENTS.md is project-scoped by definition. For those, run graftport skill > skill.md and place / paste the file by hand.

Output

Every command emits JSON on stdout by default. --pretty switches to a human-readable rendering. Exit code is 0 only when every sampled row passes validation.

Authentication

graftport auth login opens your browser, sends you through the same sign-in the dashboard uses, and asks you to authorize the CLI on a single "Authorize Graftport CLI" screen. It then stores the session in ~/.graftport/config.json:

  • postgrest_url — the Supabase PostgREST root (.../rest/v1)
  • validate_url — the FastAPI deployment that exposes /api/validate
  • app_url — the Graftport web app used for the browser sign-in
  • apikey — the project's anon key
  • access_token — the Supabase access JWT
  • refresh_token — used to silently mint a new access JWT when the current one expires (every CLI call refreshes on demand on 401)
  • expires_at — epoch seconds for the access token

The URLs and anon key default to hosted Graftport, baked into the wheel at publish time. Tokens are managed for you; you should only need to re-run graftport auth login when the refresh token expires or you explicitly auth logout.

How the browser flow works

This is the standard RFC 8252 loopback OAuth pattern (the same gh auth login --web, vercel, supabase login, and gcloud auth login use):

  1. The CLI picks a free 127.0.0.1 port and starts a one-shot HTTP server on it.
  2. The CLI opens your browser to <app-url>/cli-auth?state=…&port=…&v=1 (and prints the URL too, in case your browser doesn't open).
  3. The web app makes sure you are signed in, asks you to authorize the CLI, and POSTs the resulting Supabase session to http://127.0.0.1:<port>/callback.
  4. The CLI validates the state, writes the session to disk, and the browser tab shows "You can close this tab".

If you are on a server without a browser, pass --no-browser to print the URL and open it on a different machine. The CLI keeps listening on the same loopback port.

Headless / CI

If you already have a JWT (CI pipeline, automation script, headless container without an available browser), pass it directly 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 have no refresh_token, so you'll need to re-run auth login when the JWT expires.

Self-hosted

Override every URL via flags or GRAFTPORT_DEFAULT_* environment variables — useful for pointing the browser flow at a local Next.js dev server:

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 \
    --app-url       https://<your-web-app>.example.com

Local development

The repo this CLI lives in (bytetide-io/mono) ships an older local-only harness at scripts/test_mapping.py that connects to Postgres directly with psycopg2. It's still there for cases where you have local DB credentials and want to skip the HTTP round-trip, but the canonical agent-facing tool is graftport.

Cutting a release

See PUBLISHING.md for the tag → PyPI flow.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graftport-0.1.1.tar.gz (42.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graftport-0.1.1-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file graftport-0.1.1.tar.gz.

File metadata

  • Download URL: graftport-0.1.1.tar.gz
  • Upload date:
  • Size: 42.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for graftport-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9047459764a4caa7d8800b5e5a4db0fa06c4ba4bce7dcfe6d4ba5000cccf452f
MD5 8585c94b6c4489cac54019cd9491ce5c
BLAKE2b-256 4fd08dac9144bf23eba14443b5bec31513707ca2fbcfc04d47ae0d30628f08b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for graftport-0.1.1.tar.gz:

Publisher: publish-graftport.yml on bytetide-io/mono

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file graftport-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: graftport-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 52.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for graftport-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e766576cf64df57eb7b906fdead0f89f02b70141d23cc9d5499da315c501d66f
MD5 7a5e73577455ccdb7ff6bdd2645b1bbb
BLAKE2b-256 87bfc4b4e6d0598df9f3ea7d9ce90741c052bec21774410b7fb1532a84397cf1

See more details on using hashes here.

Provenance

The following attestation bundles were made for graftport-0.1.1-py3-none-any.whl:

Publisher: publish-graftport.yml on bytetide-io/mono

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page