cross-post-etsy-to-google
pink
#196 / 601
NAME
cross-post-etsy-to-google — Etsy publish → adapt + push to Google Shopping. Field mapping + category translation + Etsy-ism scrub.
SYNOPSIS
id cross-post-etsy-to-google
tier pink
section §7. Pink — Sakura local (65 baseline, 131 post-matrix)
trigger event:listing-published-to-etsy
tools merchant_center/sync
source new
DESCRIPTION
Sakura's local 8B model handles this on-device. Free, private, Cortex-grounded. The 'I just know what you have' tier.
Etsy publish → adapt + push to Google Shopping. Field mapping + category translation + Etsy-ism scrub.
MECHANISM
;;~ title "Cross-post · Etsy → Google Shopping" ;;~ author "lacuna" ;;~ version 1 ;;~ mode automation ;;~ flavor purple ;;~ id cross-post-etsy-to-google ;;~ touches (etsy-listings google-merchant) ;;~ summary "Event-triggered on listing-published-to-etsy. Sakura ADAPTS the listing (Etsy-isms scrubbed, category translated, fields mapped) and pushes to Google Shopping. Tier-gated to Magic+ ($39.99). Local-drafts NEVER sync — only published listings." ;; Cross-shop posting cart. Triggered when an Etsy publish completes. ;; The state machine mirrors the action-wrapper Abel established: ;; start → preconditions → adapt → push → result → done ;; (each step is named, no inline lambdas, serialisable) ;; ;; Adaptation = field mapping + format conversion + category translation. ;; The actual mapping work happens server-side in listing_mapper.py; the ;; cart's job is sequencing the calls + handling failure cleanly. (cart 'cross-post-etsy-to-google '((author . "lacuna") (version . 1) (touches . (etsy-listings google-merchant)) (read-only . #f))) ;; ── start — precondition_fetch ────────────────────────────────── ;; The driver invokes this with ctx carrying the just-published Etsy ;; listing id. We need the full listing record + the operator's ;; merchant-center status before we can adapt. (define (start ctx) (let ((id (ctx-get 'listing-id ctx))) (if (null? id) (escalate 'missing-listing-id null) (act 'etsy/listing (list id) 'check-listing)))) ;; ── check-listing — guard the precondition ────────────────────── ;; Drafts never sync (local-drafts canon). A draft has no platform_id ;; assigned; the publish event shouldn't fire for a draft, but we guard ;; anyway because the event bus can replay. (define (check-listing ctx) (let ((listing (ctx-result ctx))) (cond ((null? listing) (escalate 'listing-not-found null)) ((listing-is-draft? listing) (escalate 'drafts-do-not-sync null)) (else (next 'check-merchant (ctx-set 'listing listing ctx)))))) ;; ── check-merchant — guard the merchant connection ────────────── ;; The merchant center must be connected (operator went through the ;; OAuth flow at Settings) for the push to succeed. If not, escalate ;; so the surface can prompt the operator to connect. (define (check-merchant ctx) (act 'merchant_center.status '() 'check-merchant-result)) (define (check-merchant-result ctx) (let ((status (ctx-result ctx))) (cond ((not (status-connected? status)) (escalate 'merchant-not-connected null)) ((status-pending-verification? status) ;; Pending verification — Google hasn't finished verifying the ;; operator's account yet. Queue + retry; the sync replays ;; once the account flips to active (plan doc §2). (after 3600 'check-merchant ctx)) (else (next 'push ctx))))) ;; ── push — act ────────────────────────────────────────────────── ;; The single side-effecting state. Calls merchant_center/sync with ;; the full listing; the server-side mapper handles the translation. ;; Result handler branches in a named state fn. (define (push ctx) (let ((listing (ctx-get 'listing ctx))) (act 'merchant_center/sync (list listing) 'check-push-result))) ;; ── check-push-result — on_error grammar ──────────────────────── ;; Four outcomes: ;; · 'ok → record the google product id, done ;; · 'rate-limited → backoff 30s, retry ;; · 'mapping-failed → escalate with the closed kind so the ;; surface can show the operator which ;; fields are misshapen ;; · anything else → escalate with the closed kind (define (check-push-result ctx) (let ((r (ctx-result ctx))) (cond ((push-ok? r) (next 'announce (ctx-set 'google-product-id (push-product-id r) ctx))) ((eq? r 'rate-limited) (after 30 'push ctx)) ((eq? r 'mapping-failed) (escalate 'mapping-failed (push-errors r))) (else (escalate 'push-failed r))))) ;; ── announce — clean exit ────────────────────────────────────── ;; Emit a soft notification so the operator knows the cross-post ;; landed. The surface picks this up off the bus. (define (announce ctx) (sakura/say (string-append "Cross-posted to Google Shopping · " (ctx-get 'google-product-id ctx))) (done))
EXAMPLES
Example forthcoming —
docs/automations/examples.json entry editable in place.TIER
pink
Sakura local 8B · free · on-device
FITNESS
Ready
Fits any shop today — this one has no sample-size floor.
SEE ALSO
HISTORY
canonical source: curator/docs/AUTOMATIONS-CANONICAL.md · §7-pink-sakura-local-65-baseline-131-post-matrix
last regenerated: 2026-06-05 20:31 UTC (mechanical — edits land in the MD or sidecars, not here)
KNOWN BUGS
(none recorded — flag in AUTOMATIONS-CANONICAL.md or open an issue against the cart file)