Files
zeekay b4f8d7b2f4 feat(shopify): Hanzo AI for Shopify — embedded admin app on the shared foundation
Add packages/shopify (@hanzo/shopify): a Shopify embedded admin app that puts
Hanzo AI over the store as system of record.

- Product content: generate/rewrite descriptions + SEO title/meta from product
  attributes, then write back via the productUpdate mutation (only changed fields).
- Orders/support: summarize an order, draft a customer reply, extract issues.
- Ask: freeform question over a product or order, grounded in its data.

Server (src/server, holds the only secrets):
- oauth.ts   Shopify OAuth request shaping + OAuth-callback HMAC verify (hex over
             Shopify-canonical URLSearchParams form) + state (CSRF) check.
- shopify-api.ts  Admin GraphQL wrappers (getProduct/getOrder/updateProduct) —
                  pure request shaping + parsing + productUpdate userErrors.
- hanzo.ts   thin over the PUBLISHED @hanzo/ai createAiClient; product/order
             context assembly + prompt building. No transport reimplemented.
- actions.ts product + order AI action catalogs, one prompt each over ask.
- webhooks.ts  webhook HMAC verify (base64 over RAW body) + topic routing
               (orders/create, app/uninstalled, the 3 GDPR topics).
- server.ts  OAuth + /v1/* proxy the frontend calls + /webhooks. HMAC verified
             before any param/body is trusted; shop validated to *.myshopify.com.

Frontend (src/app): Polaris + App Bridge React panel — api.ts (proxy client,
no secrets), context.ts (launch context → GID), session-fetch.ts (App Bridge
session-token fetch), App/Assistant/main. App Bridge loaded from Shopify CDN;
public API key stamped into the app, secret never bundled.

Plus shopify.app.toml, an admin-link extension, build.js (esbuild: app bundle +
Node server), tsconfig, vitest config, README. 108 vitest tests (pure), tsc
--noEmit clean, build green. Model calls via @hanzo/ai to api.hanzo.ai/v1 only.

Register packages/shopify in pnpm-workspace.yaml.
2026-07-03 20:53:10 -07:00

57 lines
2.0 KiB
TOML

# Hanzo AI for Shopify — app configuration (shopify app deploy reads this).
# The `client_id` is the app's PUBLIC API key from the Partner Dashboard; the
# API SECRET is never in this file — it lives only in the server's environment
# (SHOPIFY_API_SECRET). Docs: shopify.dev/docs/apps/build/cli-for-apps/app-configuration
#
# Fill `client_id` and the two URLs from your Partner app, then:
# shopify app config link # bind this toml to the Partner app
# shopify app deploy # push scopes, URLs, and the extension
name = "Hanzo AI"
handle = "hanzo-ai"
client_id = "REPLACE_WITH_PARTNER_APP_CLIENT_ID"
application_url = "https://shopify.hanzo.ai/"
embedded = true
[access_scopes]
# The only scopes the app uses: read/write products (content write-back) and
# read orders (support + order insight). Nothing we do not use.
scopes = "read_products,write_products,read_orders"
[auth]
# The OAuth callback the server verifies (HMAC + state) and exchanges the code at.
redirect_urls = [
"https://shopify.hanzo.ai/oauth/callback"
]
[webhooks]
api_version = "2025-01"
# Order creation → the server can auto-draft a support summary.
[[webhooks.subscriptions]]
topics = [ "orders/create" ]
uri = "https://shopify.hanzo.ai/webhooks"
# App uninstall → the server drops the shop's stored offline token.
[[webhooks.subscriptions]]
topics = [ "app/uninstalled" ]
uri = "https://shopify.hanzo.ai/webhooks"
# The three GDPR/privacy topics every public app must handle to pass review.
[[webhooks.subscriptions]]
topics = [ "customers/data_request", "customers/redact", "shop/redact" ]
uri = "https://shopify.hanzo.ai/webhooks"
[app_proxy]
# Not used — the embedded app talks to the server directly over its own host.
[pos]
embedded = false
[build]
# The embedded app is served as static assets over hanzoai/static; the CLI does
# not build it (build.js does). Point automatically_update_urls_on_dev off so a
# local `shopify app dev` never rewrites the production application_url.
automatically_update_urls_on_dev = false
include_config_on_deploy = true