@greatstore/cli 0.0.25 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +4 -1
- package/dist/cli.js +3 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to `@greatstore/cli` are recorded here. The format
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
|
|
6
|
+
## 0.0.27 — 2026-06-11
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Skill code samples now carry an explicit reference-only disclaimer:
|
|
10
|
+
coding agents are told to re-express the logic in the host repo's
|
|
11
|
+
framework (React, Vue, Shopify Liquid, Svelte, …) instead of
|
|
12
|
+
retrofitting the framework-free samples as-is.
|
|
13
|
+
|
|
14
|
+
## 0.0.26 — 2026-06-11
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- The agent skill gains a "GreatStore launchers" recipe: a horizontally
|
|
18
|
+
scrollable row of AI-generated chips, each an engaging first-person
|
|
19
|
+
question about the current page that's sent to the assistant on tap.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Skill recipes are now one file each under `recipes/`, indexed from
|
|
23
|
+
SKILL.md by a table with description and use-case columns.
|
|
24
|
+
|
|
6
25
|
## 0.0.25 — 2026-06-11
|
|
7
26
|
|
|
8
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# @greatstore/cli
|
|
2
2
|
|
|
3
|
-
Author and ship GreatStore custom remote components from the command
|
|
3
|
+
Author and ship GreatStore custom remote components from the command
|
|
4
|
+
line, and set up your AI coding agent to build with GreatStore.
|
|
4
5
|
|
|
5
6
|
```
|
|
6
7
|
npm install -g @greatstore/cli
|
|
8
|
+
gs skill # teach your AI coding agent to build with GreatStore
|
|
7
9
|
gs login
|
|
8
10
|
gs init --store <slug>
|
|
9
11
|
cd <project> && npm install
|
|
@@ -21,6 +23,7 @@ gs publish my-widget
|
|
|
21
23
|
| `gs logout` | Sign out. |
|
|
22
24
|
| `gs whoami` | Print the identity you're signed in as. |
|
|
23
25
|
| `gs init <name> [--out <dir>] [--store <s>]` | Scaffold a working component project. |
|
|
26
|
+
| `gs build [<name>]` | Build component bundle(s); no args = all. |
|
|
24
27
|
| `gs list [--json]` | List components in the current store. |
|
|
25
28
|
| `gs pull <name> [--draft\|--live\|--version N] [-o <dir>]` | Download a component to disk. |
|
|
26
29
|
| `gs push [<name>] [--manifest path] [--bundle path]` | Upload local changes. |
|
package/dist/cli.js
CHANGED
|
@@ -1490,9 +1490,7 @@ var SKILL_DIR_NAME = "greatstore";
|
|
|
1490
1490
|
var cache2 = null;
|
|
1491
1491
|
function loadSkill() {
|
|
1492
1492
|
if (cache2) return cache2;
|
|
1493
|
-
cache2 = true ? JSON.parse('{"SKILL.md":"---\\nname: greatstore\\ndescription: Build AI-powered shopping experiences with GreatStore on a merchant\'s website and store. Use when installing the GreatStore chat widget on a site, generating AI content for custom UI with generateStructuredContent, adding chat entry points (sendMessage), letting the assistant act on the page via WebMCP page tools (document.modelContext.registerTool), authoring custom in-chat React components with the gs CLI, setting up web push re-engagement, or connecting AI agents to a store\'s MCP endpoints. Covers setup, schema design, caching behavior, component authoring, and ready-made recipes.\\n---\\n\\n# Building with GreatStore\\n\\nGreatStore gives a store an AI shopping assistant on two surfaces: a hosted\\nstorefront at `https://<slug>.greatstore.ai/`, and an embedded chat widget on\\nthe merchant\'s own site, installed with one script tag:\\n\\n```html\\n<script src=\\"https://my-store.greatstore.ai/embed.js\\"></script>\\n```\\n\\nEverything else a site can do with GreatStore is documented in the\\nreferences below. Read the one that matches the task before writing code \u2014\\neach facet has non-obvious rules (caching, grounding, result shapes, design\\nconstraints) that the references spell out.\\n\\n## Index\\n\\n| Goal | Use | Read |\\n|---|---|---|\\n| Install the widget; control the panel; readiness, events, troubleshooting | `window.GreatStore` SDK | [references/embed-api.md](references/embed-api.md) |\\n| AI-generated, catalog-grounded content rendered in **your own HTML/CSS** (highlights, comparisons, FAQs, gift guides) | `generateStructuredContent(schema, prompt)` | [references/structured-content.md](references/structured-content.md) |\\n| Copy-paste on-page experiences (gift-finder funnel, comparison table, FAQ accordion, campaign hero, custom launcher) | recipes built on the SDK | [references/page-recipes.md](references/page-recipes.md) |\\n| Contextual **conversation entry points** anywhere on the page | `sendMessage(text)`, `open()`, `?gs_chat=open` | [references/embed-api.md](references/embed-api.md) |\\n| Let the assistant **act on the page** (cart, navigation, filters) | WebMCP: `document.modelContext.registerTool(...)` | [references/embed-api.md](references/embed-api.md) |\\n| Custom **interactive UI inside the chat** (configurators, quizzes, size guides, booking forms) | remote components shipped with the `gs` CLI | [references/chat-components.md](references/chat-components.md) |\\n| **Re-engage shoppers** with browser notifications | merchant-hosted `gs.js` + `enableNotifications()` | [references/push-notifications.md](references/push-notifications.md) |\\n| Connect **AI agents** to the store (shopping tools over MCP, CLI docs for coding agents) | the store\'s MCP endpoints | [references/agents-and-mcp.md](references/agents-and-mcp.md) |\\n\\n## Two things to know before any of it\\n\\n- The page\'s domain **must be in the store\'s allowed domains** (GreatStore\\n store settings). If it isn\'t, nothing works and the console shows\\n `[GreatStore] Chat is unavailable on <origin>\u2026` \u2014 check this first whenever\\n the embed appears dead.\\n- Every SDK call is safe immediately after the script tag \u2014 pre-mount calls\\n queue and replay in order, and the SDK pre-warms itself in the background.\\n\\n## How the facets combine\\n\\nThe strongest pattern is the **teaser \u2192 conversation \u2192 action** funnel:\\n`generateStructuredContent` renders a grounded teaser in the merchant\'s\\ndesign; each option\'s click handler calls `sendMessage` with the shopper\'s\\nchoice, dropping them into a conversation with momentum; WebMCP page tools\\nand custom chat components let that conversation actually do things \u2014 add to\\ncart, configure a product, book a slot \u2014 so it ends in a conversion, not a\\ncopy-paste.\\n","references/agents-and-mcp.md":"# AI agents and the store\'s MCP endpoints\\n\\nBeyond the widget, every GreatStore store is reachable by AI agents directly\\nover the Model Context Protocol. Three URLs, all on the store\'s subdomain,\\nnone requiring authentication:\\n\\n| URL | What it is |\\n|---|---|\\n| `https://<slug>.greatstore.ai/.well-known/mcp/server-card.json` | Standard MCP server card \u2014 machine-readable discovery document for the store. |\\n| `https://<slug>.greatstore.ai/mcp` | The store\'s **shopping tools** over MCP \u2014 product search and related catalog actions, the same capabilities the assistant itself shops with. |\\n| `https://<slug>.greatstore.ai/admin-mcp` | A **documentation server for coding agents**: its tools return usage docs for the `gs` CLI (login, init, build, push, publish). |\\n\\n## Shopping tools \u2014 `/mcp`\\n\\nConnect any MCP-capable agent to let it browse and shop the store\\nprogrammatically. This is the integration point for agentic-shopping\\nclients, comparison bots, or the merchant\'s own automations that need live\\ncatalog answers.\\n\\n## CLI docs for coding agents \u2014 `/admin-mcp`\\n\\nA stateless HTTP MCP whose tools hand back markdown documentation for `gs`\\nCLI commands. When accessed via the store\'s subdomain, every example comes\\npre-pinned to that store\'s slug, so the agent never has to ask which store\\nto target.\\n\\n```\\nclaude mcp add --transport http greatstore https://my-store.greatstore.ai/admin-mcp\\n```\\n\\nUseful when a coding agent is shipping chat components\\n([chat-components.md](chat-components.md)) and needs the exact command for\\nthe next step. If this skill is installed, the agent already has the\\nworkflow \u2014 the MCP is the self-serve alternative for agents that don\'t.\\n\\n## What to use when\\n\\n- **Building the merchant\'s site** \u2192 this skill\'s other references (the SDK,\\n structured content, page tools).\\n- **A coding agent shipping chat components** \u2192 the `gs` CLI, with\\n `/admin-mcp` as its built-in documentation.\\n- **An agent that needs to *shop* the store** (search products, read catalog\\n data) from outside any web page \u2192 `/mcp`.\\n","references/chat-components.md":"# Custom chat components \u2014 authoring with the `gs` CLI\\n\\nRemote components are React components the assistant renders **inside the\\nconversation** \u2014 product configurators, quizzes, size guides, booking forms,\\nanything richer than text. Each component is an AI-callable tool: the\\nmanifest\'s `description` tells the assistant *when* to show it, its\\n`inputSchema` declares the props the assistant fills in, and a `displayMode`\\npicks where it appears.\\n\\nAuthoring requires store-owner access (`gs login` signs in with the store\\nowner\'s account).\\n\\n## Workflow\\n\\n```\\nnpm install -g @greatstore/cli # or npx @greatstore/cli <command>\\ngs login # browser sign-in\\ngs init --store my-store # scaffold a project root\\ncd <project> && npm install\\ngs init size_guide # scaffold components/size_guide/\\n# \u2026 edit components/size_guide/{component.tsx,manifest.json} \u2026\\ngs build # bundle every component\\ngs push # upload changed components as drafts\\ngs publish size_guide # promote to live\\n```\\n\\n`gs list` shows what\'s deployed (with dashboard links); `gs pull` round-trips\\nremote components back to disk. `gs push` hashes components and only uploads\\nwhat changed.\\n\\nThe scaffold writes an `AGENTS.md` into the project (with `CLAUDE.md` /\\n`GEMINI.md` symlinked) containing the complete design rules and brand\\nvariable table \u2014 your coding agent picks it up automatically when working in\\nthe project. The store\'s `/admin-mcp` endpoint serves the same CLI docs to\\nagents over MCP (see [agents-and-mcp.md](agents-and-mcp.md)).\\n\\n## `manifest.json`\\n\\n```json\\n{\\n \\"name\\": \\"size_guide\\",\\n \\"displayName\\": \\"Size guide\\",\\n \\"description\\": \\"Interactive size guide. Show when the shopper asks about sizing or fit for apparel.\\",\\n \\"displayMode\\": \\"inline\\",\\n \\"inputSchema\\": {\\n \\"type\\": \\"object\\",\\n \\"properties\\": {\\n \\"productName\\": { \\"type\\": \\"string\\" },\\n \\"category\\": { \\"type\\": \\"string\\" }\\n },\\n \\"required\\": [\\"productName\\"]\\n }\\n}\\n```\\n\\n| Field | Meaning |\\n|---|---|\\n| `name` | Tool name, snake_case (`^[a-z][a-z0-9_]*$`), matches the folder under `components/`. |\\n| `displayName` | Friendly label shown in chat UI. |\\n| `description` | **Load-bearing** \u2014 how the assistant decides when to render the component. Say what it shows *and* when to use it, like any good tool description. |\\n| `displayMode` | Where it renders \u2014 see below. |\\n| `inputSchema` | JSON Schema for the props the assistant fills. Keep it tight; required fields the AI can\'t infer cause bad calls. |\\n| `async` | Set `true` for backend-backed components (see Async below). |\\n\\n### Display modes\\n\\n- `inline` \u2014 a bubble inside the chat transcript; persists with the message\\n log.\\n- `over-input` \u2014 floats above the chat input (like a question overlay);\\n cleared by the next user turn or an explicit close.\\n- `fullscreen` \u2014 takes over the full preview surface; persists until the\\n next widget-emitting tool call or an explicit close.\\n\\n## The component contract\\n\\n`component.tsx` default-exports a React component. Its props are the\\n`inputSchema` fields the assistant filled, plus three GreatStore-injected\\nlifecycle props (always present):\\n\\n| Prop | What it does |\\n|---|---|\\n| `onSendMessage(text)` | Send text into the chat as if the shopper typed it \u2014 lets the component drive the conversation (\\"Selected size M, what\'s the return policy?\\"). |\\n| `onCallTool(name, args)` | Chain into another remote-component tool by name. |\\n| `onClose()` | Dismiss the host slot. `over-input` clears the overlay, `fullscreen` reverts the pane, `inline` is a no-op. |\\n\\n```tsx\\nimport React from \\"react\\";\\n\\ninterface Props {\\n productName: string;\\n category?: string;\\n onSendMessage: (text: string) => void;\\n onCallTool: (name: string, args: Record<string, unknown>) => void;\\n onClose: () => void;\\n}\\n\\nexport default function SizeGuide({ productName, onSendMessage }: Props) {\\n return (\\n <div\\n style={{\\n padding: \\"1em\\",\\n border: \\"1px solid var(--color-border-default)\\",\\n borderRadius: \\"var(--radius-lg)\\",\\n background: \\"var(--color-surface)\\",\\n color: \\"var(--color-foreground)\\",\\n fontFamily: \\"var(--font-sans)\\",\\n }}\\n >\\n {/* \u2026 sizes for {productName} \u2026 */}\\n <button onClick={() => onSendMessage(`Size M of \\"${productName}\\" \u2014 is it in stock?`)}>\\n Check size M\\n </button>\\n </div>\\n );\\n}\\n```\\n\\n## Design rules (non-negotiable)\\n\\nComponents render inside arbitrary publisher pages *and* the GreatStore\\nstorefront; you control neither the host\'s root font size nor its colors.\\n\\n1. **Size in `em`, never `rem`** \u2014 `rem` resolves against the host page\'s\\n root font size, which is arbitrary (`html { font-size: 8px }` breaks every\\n `rem` dimension). `em` stays self-consistent anywhere. Borders may stay\\n `px`.\\n2. **Never hardcode colors, fonts, or radii** \u2014 read the brand CSS variables\\n GreatStore injects (`--color-primary`, `--color-surface`,\\n `--color-foreground`, `--color-border-default`, `--font-sans`,\\n `--radius-lg`, \u2026) so the component restyles itself with the store\'s\\n theme. The scaffolded `AGENTS.md` has the full variable table.\\n\\n## Async components (backend-backed data)\\n\\nIf a component must load data before it can render correctly, don\'t render a\\nshell and fetch in `useEffect` \u2014 set `\\"async\\": true` in the manifest and\\nexport an **async** default. GreatStore shows its own loading state, awaits\\nyour promise, and renders what it resolves to.\\n\\nA thrown error is a **retry signal**: the assistant sees it and usually\\nre-calls the tool. So only throw when a *different* call could help:\\n\\n1. Validate the AI-passed props first and throw on bad input \u2014 the AI can\\n fix the args and retry. (Don\'t validate the API\'s *output* and throw: the\\n AI can\'t fix your backend, it\'ll just loop.)\\n2. Throw on failures where retrying differently could succeed, and say what\\n to change (e.g. empty search \u2192 `\\"no results for X \u2014 try a broader keyword\\"`).\\n3. For idempotent failures (500, timeout, missing record) render a graceful\\n fallback instead of throwing \u2014 re-running the same call changes nothing.\\n\\n```tsx\\nexport default async function Results(props: Props) {\\n if (!props.query?.trim()) throw new Error(\\"missing required prop: query\\");\\n const res = await fetch(`/api/search?q=${encodeURIComponent(props.query)}`);\\n if (res.ok) {\\n const { results } = await res.json();\\n if (results.length === 0)\\n throw new Error(`no results for \\"${props.query}\\" \u2014 try a broader keyword`);\\n return <ul>{/* render results */}</ul>;\\n }\\n return <p>Couldn\'t load results right now.</p>; // idempotent: don\'t throw\\n}\\n```\\n\\n## When to build a component vs. the other facets\\n\\n- Content for the **merchant\'s page** \u2192 `generateStructuredContent`\\n ([structured-content.md](structured-content.md)).\\n- Letting the assistant **act on the page** \u2192 WebMCP page tools\\n ([embed-api.md](embed-api.md)).\\n- Rich, interactive UI **inside the conversation itself**, available on the\\n storefront and every embed without page changes \u2192 a chat component.\\n","references/embed-api.md":"# `window.GreatStore` API reference\\n\\n## Setup\\n\\n```html\\n<script src=\\"https://my-store.greatstore.ai/embed.js\\"></script>\\n```\\n\\nOne script tag, anywhere on the page (end of `<body>` preferred), with the\\nstore\'s slug in the host. The `window.GreatStore` object exists synchronously\\nonce the script executes; every method below is safe to call before the chat\\nUI has finished loading \u2014 pre-mount calls are queued and replayed in order\\nonce it mounts. The SDK pre-warms its chat bundle in the background\\nautomatically; the panel stays closed until `open()` / `toggle()` /\\n`sendMessage()` is called or the shopper clicks the launcher.\\n\\nRequirements:\\n\\n- The page\'s domain must be in the store\'s **allowed domains** (GreatStore\\n store settings) \u2014 see Troubleshooting below for the failure signature.\\n- If the store also wants push notifications, host `gs.js` at the site root\\n and load that instead of `embed.js` \u2014 it injects the embed for you. See\\n [push-notifications.md](push-notifications.md).\\n\\n## Properties\\n\\n| Property | Type | Description |\\n|---|---|---|\\n| `slug` | `string` | The store identifier the script was loaded for. |\\n| `host` | `string` | `\\"greatstore.ai\\"`. |\\n| `embedHost` | `string` | Origin the embed assets load from, e.g. `https://<slug>.greatstore.ai`. |\\n| `ready` | `Promise<void>` | Resolves when the chat UI has mounted and `open()` would render instantly. Resolved promises replay, so `.then()` works no matter when it\'s attached. The recommended readiness signal. |\\n| `isReady` | `boolean` | Synchronous alternative to `ready`. `false` until mount. |\\n\\nA `greatstore:ready` `CustomEvent` (with the SDK object as `detail`) is also\\ndispatched on `window` at the moment `ready` resolves, for declarative\\ntooling. Unlike the promise, the listener must be attached before mount\\ncompletes \u2014 attach it before (or immediately after) the embed script tag.\\n\\n## Methods\\n\\n### `load(): void`\\n\\nPre-warms the chat bundle and identity in the background without opening the\\npanel. Called automatically when `embed.js` runs, so you rarely need it.\\nIdempotent.\\n\\n### `open(): void` / `close(): void` / `toggle(): void`\\n\\nOpen, close, or toggle the chat panel. On desktop the panel is a floating\\nside panel; under 768px viewport width it\'s a full-height drawer. All three\\nqueue if called before mount.\\n\\n### `sendMessage(text: string): void`\\n\\nSends `text` as the shopper\'s own visible chat message and **opens the panel\\nif it\'s closed**. The text is trimmed; empty or whitespace-only strings are\\nsilently dropped. Queues if called before mount.\\n\\nThis is the highest-leverage one-liner in the SDK: any element on the page\\ncan become a conversation entry point with context baked into the question.\\n\\n```js\\ndocument.querySelector(\\"#ask-fit\\").addEventListener(\\"click\\", () => {\\n window.GreatStore.sendMessage(\\n `I\'m looking at \\"${productName}\\" \u2014 how does the sizing run?`\\n );\\n});\\n```\\n\\nBecause the message renders as if the shopper typed it, write it in the\\nshopper\'s voice. It is not a hidden-context channel \u2014 don\'t stuff it with\\ninvisible instructions or data dumps.\\n\\n### `on(event: string, handler: (...args) => void): () => void`\\n\\nSubscribe to SDK events. Returns an unsubscribe function. Listeners attached\\nbefore mount are queued and wired up at mount. Handler exceptions are caught\\nand reported \u2014 they won\'t break the chat.\\n\\nEvents emitted:\\n\\n| Event | Fired when |\\n|---|---|\\n| `\\"open\\"` | Panel transitions closed \u2192 open (including via `sendMessage` or the shopper\'s own click). |\\n| `\\"close\\"` | Panel transitions open \u2192 closed. |\\n\\n### `generateStructuredContent(schema: object, prompt: string): Promise<unknown>`\\n\\nGenerates JSON matching `schema` from `prompt`, grounded in the store\'s live\\ncatalog. Resolves to the generated data object itself. Rejects with `Error`\\non any failure (invalid input, decline, validation failure, rate limit,\\nnetwork). See [structured-content.md](structured-content.md) for the full\\ncontract, schema support, caching, and error semantics.\\n\\nAccepts either a plain JSON Schema object or any object exposing a\\n`.toJSONSchema()` method (e.g. Zod schemas) \u2014 the conversion is called for\\nyou.\\n\\nThrows synchronously (rejects) if `prompt` is not a non-empty string or\\n`schema` is not an object.\\n\\n### `enableNotifications(): Promise<{ ok: boolean }>`\\n\\nOpts this browser into Web Push notifications from the store. Requirements:\\n\\n- Must be called from a user gesture (e.g. a click handler).\\n- The site must host GreatStore\'s `gs.js` service-worker file. By default the\\n SDK looks for it at `/gs.js`; if it\'s hosted elsewhere, point to it via an\\n attribute on the embed script tag:\\n `<script src=\\"\u2026/embed.js\\" data-push-sw-path=\\"/path/to/gs.js\\"></script>`.\\n\\nResolves `{ ok: true }` on success and `{ ok: false }` on any failure\\n(unsupported browser, no service worker hosted, permission denied). It never\\nrejects.\\n\\n## Page tools \u2014 WebMCP (`document.modelContext`)\\n\\nThe recommended way to expose page capabilities to the assistant is the\\nWebMCP standard. The GreatStore assistant discovers every tool registered on\\n`document.modelContext`, re-reading the list on each conversational turn \u2014\\nso tools registered mid-session appear on the next message without a reload.\\n\\n### Availability\\n\\nIf the browser implements WebMCP natively, `document.modelContext` is just\\nthere. Otherwise the SDK installs a spec-tracking polyfill on the page\\nautomatically \u2014 but asynchronously, so at your script\'s first run\\n`document.modelContext` may not exist yet. Two robust patterns:\\n\\n```js\\n// 1. Register once GreatStore is ready (polyfill is in place by then):\\nwindow.GreatStore?.ready.then(() => {\\n document.modelContext.registerTool(/* \u2026 */);\\n});\\n\\n// 2. Or ship your own polyfill (npm: @mcp-b/webmcp-polyfill) and register\\n// immediately \u2014 the SDK detects an existing implementation and uses it.\\n```\\n\\n(`navigator.modelContext` is a deprecated alias for the same object; use\\n`document.modelContext` in new code.)\\n\\n### `registerTool(tool, options?)`\\n\\n```ts\\ndocument.modelContext.registerTool(\\n {\\n name: string, // required, non-empty, unique on the page\\n description: string, // required \u2014 how the AI decides when to call it\\n inputSchema?: object, // JSON Schema for execute\'s args;\\n // defaults to { type: \\"object\\", properties: {} }\\n execute(args): Result | Promise<Result>,\\n },\\n options?: { signal?: AbortSignal }, // abort to unregister\\n);\\n```\\n\\n- **Result shape**: `execute` returns MCP content blocks \u2014\\n `{ content: [{ type: \\"text\\", text: \\"\u2026\\" }] }`. For structured data,\\n `JSON.stringify` it into `text`. Add `isError: true` to mark a handled\\n failure.\\n- **Errors**: a thrown error or rejected promise is delivered to the\\n assistant as a *failed* tool call carrying the error message \u2014 the\\n assistant can explain or adapt. Errors never escape into your page.\\n- **Duplicate names throw.** To replace a tool, abort its registration first.\\n- **Unregistration is `AbortSignal`-driven**: pass `{ signal }` and call\\n `abort()` when the tool\'s context goes away (SPA navigation, modal close).\\n A pre-aborted signal skips registration. (A legacy\\n `unregisterTool(name)` exists but is deprecated in the spec.)\\n- **Treat `args` as untrusted input**: values are AI-generated. Validate\\n before passing to your own APIs, and never `eval` anything from them.\\n\\nA complete tool, registered once GreatStore is ready:\\n\\n```js\\nwindow.GreatStore?.ready.then(() => {\\n document.modelContext.registerTool({\\n name: \\"add_to_cart\\",\\n description:\\n \\"Add a product variant to the shopper\'s cart on this site. \\" +\\n \\"Use when the shopper asks to add, buy, or get a product.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n variantId: { type: \\"string\\" },\\n quantity: { type: \\"integer\\", minimum: 1 },\\n },\\n required: [\\"variantId\\"],\\n },\\n async execute({ variantId, quantity }) {\\n const res = await fetch(\\"/cart/add.js\\", {\\n method: \\"POST\\",\\n headers: { \\"Content-Type\\": \\"application/json\\" },\\n body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),\\n });\\n if (!res.ok) throw new Error(`Cart add failed (${res.status})`);\\n const cart = await res.json();\\n return { content: [{ type: \\"text\\", text: JSON.stringify(cart) }] };\\n },\\n });\\n});\\n```\\n\\nReturning the fresh cart state after the mutation lets the assistant confirm\\naccurately. Good tool families: cart (`get_cart`, `add_to_cart`), navigation\\n(`go_to_page`), page state (`get_current_product`, `apply_filters`), UI\\n(`highlight_section`, `scroll_to_reviews`).\\n\\nAnd a context-scoped tool, unregistered via `AbortSignal`:\\n\\n```js\\nconst ac = new AbortController();\\ndocument.modelContext.registerTool(\\n {\\n name: \\"get_product_reviews\\",\\n description: \\"Read the reviews shown on the current product page.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n execute: () => ({\\n content: [{ type: \\"text\\", text: JSON.stringify(collectReviews()) }],\\n }),\\n },\\n { signal: ac.signal },\\n);\\n\\n// On SPA route change away from the product page:\\nac.abort();\\n```\\n\\n## URL parameter: `?gs_chat=open`\\n\\nWhen the page URL carries `gs_chat=open`, the panel opens automatically once\\nthe embed mounts. The param is consumed and stripped from the URL via\\n`history.replaceState`, so a manual reload doesn\'t re-open the panel. Use it\\nin campaign links, emails, and post-login redirects.\\n\\n## Shopify login gate\\n\\nStores can enable a per-store \\"log in to chat\\" gate that applies **only to\\nthe embed** (never the store\'s GreatStore storefront). When enabled, the chat\\ninput is replaced with a login prompt until a Shopify customer signal is\\ndetected on the host page (standard Shopify globals/meta tags). The login\\nbutton sends the shopper through Shopify\'s customer login and back with\\n`?gs_chat=open` appended. This is a courtesy UX gate, not a security\\nboundary \u2014 don\'t rely on it to protect anything sensitive.\\n\\n## Troubleshooting\\n\\n| Symptom | Likely cause |\\n|---|---|\\n| Console: `[GreatStore] Chat is unavailable on <origin>\u2026 this domain isn\'t in the store\'s allowed domains` | The page\'s origin isn\'t in the store\'s allowed domains. Add it in store settings. Until then every SDK network call fails. |\\n| Console: `[GreatStore] Embed script must be loaded from <slug>.greatstore.ai/embed.js` | The script was copied/self-hosted instead of loaded from the store\'s embed URL. Always load it from `https://<slug>.greatstore.ai/embed.js`. |\\n| `generateStructuredContent` rejects with a rate-limit message | More than ~20 requests/minute from one visitor. Consolidate calls into fewer, richer schemas. |\\n| Panel won\'t auto-open on mobile after returning to the page | Intentional: the mobile drawer never auto-opens on resume \u2014 it would cover the content the shopper is reading. The transcript is preserved; they\'ll see it when they tap the launcher. |\\n| Tools registered but the assistant doesn\'t use them | Check the `description` \u2014 it\'s the only signal for *when* to call. Also confirm the registration ran (`document.modelContext` exists after `ready`) on the same page the conversation is on. |\\n","references/page-recipes.md":"# Experience recipes\\n\\nComplete, framework-free implementations. Shared conventions:\\n\\n- Containers start `hidden`; reveal only on success. A failed generation\\n changes nothing on the page.\\n- Generated strings render via `textContent` / `append`, never `innerHTML`.\\n- Every recipe guards on `window.GreatStore` so the page works if the embed\\n is blocked or absent.\\n- Prompts are deterministic per page so every visitor after the first hits\\n the cache (see [structured-content.md](structured-content.md)).\\n\\nReplace product/page data interpolations with whatever your platform exposes\\n(Liquid, JSON-LD, a data attribute). Inlining the product *name and key\\nfacts* into the prompt beats relying on the page-title hint.\\n\\n---\\n\\n## 1. Gift finder funnel (teaser \u2192 conversation \u2192 action)\\n\\nThe flagship pattern: an AI-generated quiz card in your design, whose answers\\ndrop the shopper into a chat that can act on the page.\\n\\n```html\\n<section id=\\"gift-finder\\" hidden>\\n <h3 id=\\"gf-question\\"></h3>\\n <div id=\\"gf-options\\"></div>\\n</section>\\n\\n<script>\\n (async () => {\\n const gs = window.GreatStore;\\n if (!gs?.generateStructuredContent) return;\\n\\n // Tools the resulting conversation can use. Registered once the\\n // SDK is ready (so document.modelContext exists); the assistant\\n // discovers them on its next turn automatically.\\n gs.ready.then(() => {\\n document.modelContext.registerTool({\\n name: \\"go_to_product\\",\\n description:\\n \\"Navigate the shopper to a product page on this site. Use when \\" +\\n \\"the shopper picks a product they want to see.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: { url: { type: \\"string\\" } },\\n required: [\\"url\\"],\\n },\\n execute({ url }) {\\n const u = new URL(String(url), location.origin);\\n if (u.origin !== location.origin) throw new Error(\\"Only same-site URLs allowed\\");\\n location.assign(u.href);\\n return { content: [{ type: \\"text\\", text: \\"Navigating.\\" }] };\\n },\\n });\\n });\\n\\n try {\\n const data = await gs.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n question: { type: \\"string\\", maxLength: 80 },\\n options: {\\n type: \\"array\\",\\n minItems: 3,\\n maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n label: { type: \\"string\\", maxLength: 30 },\\n chatMessage: { type: \\"string\\", maxLength: 140 },\\n },\\n required: [\\"label\\", \\"chatMessage\\"],\\n },\\n },\\n },\\n required: [\\"question\\", \\"options\\"],\\n },\\n \\"Create one engaging gift-finder opening question for this store, \\" +\\n \\"with 3-4 answer options that map to real areas of the catalog. \\" +\\n \\"For each option also write chatMessage: the message a shopper \\" +\\n \\"would send to a shopping assistant after picking it, phrased in \\" +\\n \\"first person (e.g. \\\\\\"I\'m shopping for my dad who loves hiking\\\\\\").\\"\\n );\\n\\n document.getElementById(\\"gf-question\\").textContent = data.question;\\n const wrap = document.getElementById(\\"gf-options\\");\\n for (const opt of data.options) {\\n const btn = document.createElement(\\"button\\");\\n btn.type = \\"button\\";\\n btn.textContent = opt.label;\\n btn.addEventListener(\\"click\\", () => gs.sendMessage(opt.chatMessage));\\n wrap.append(btn);\\n }\\n document.getElementById(\\"gift-finder\\").hidden = false;\\n } catch {}\\n })();\\n</script>\\n```\\n\\nWhy it works: the teaser costs one cached generation for all visitors, each\\nclick opens a conversation that already has direction, and `go_to_product`\\nlets the conversation end on a product page instead of in a dead end.\\n\\n---\\n\\n## 2. Collection-page comparison table\\n\\n```html\\n<section id=\\"gs-compare\\" hidden>\\n <h3>Quick comparison</h3>\\n <table><thead id=\\"gsc-head\\"></thead><tbody id=\\"gsc-body\\"></tbody></table>\\n</section>\\n\\n<script>\\n (async () => {\\n if (!window.GreatStore?.generateStructuredContent) return;\\n const collection = \\"winter jackets\\"; // \u2190 your collection name\\n try {\\n const data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n criteria: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: { type: \\"string\\", maxLength: 25 },\\n },\\n rows: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n productName: { type: \\"string\\" },\\n bestFor: { type: \\"string\\", maxLength: 60 },\\n values: {\\n type: \\"array\\",\\n items: { type: \\"string\\", maxLength: 60 },\\n },\\n },\\n required: [\\"productName\\", \\"bestFor\\", \\"values\\"],\\n },\\n },\\n },\\n required: [\\"criteria\\", \\"rows\\"],\\n },\\n `The shopper is browsing the \\"${collection}\\" collection. Pick the ` +\\n `3-4 most representative products and compare them. Choose the ` +\\n `criteria a shopper actually decides on for this category. ` +\\n `\\"values\\" must align with \\"criteria\\" by index. Add a one-line ` +\\n `\\"bestFor\\" verdict per product. Only use real products and facts.`\\n );\\n\\n const head = document.getElementById(\\"gsc-head\\");\\n const hr = document.createElement(\\"tr\\");\\n for (const h of [\\"Product\\", ...data.criteria, \\"Best for\\"]) {\\n const th = document.createElement(\\"th\\");\\n th.textContent = h;\\n hr.append(th);\\n }\\n head.append(hr);\\n\\n const body = document.getElementById(\\"gsc-body\\");\\n for (const row of data.rows) {\\n const tr = document.createElement(\\"tr\\");\\n const cells = [row.productName, ...(row.values ?? []), row.bestFor];\\n for (let i = 0; i < data.criteria.length + 2; i++) {\\n const td = document.createElement(\\"td\\");\\n td.textContent = cells[i] ?? \\"\u2014\\";\\n tr.append(td);\\n }\\n body.append(tr);\\n }\\n document.getElementById(\\"gs-compare\\").hidden = false;\\n } catch {}\\n })();\\n</script>\\n```\\n\\nNote the index-aligned `values`/`criteria` trick and the `?? \\"\u2014\\"` guard \u2014\\ngrounding means a value the catalog can\'t support may be missing.\\n\\n---\\n\\n## 3. Product FAQ accordion\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n faqs: {\\n type: \\"array\\", minItems: 3, maxItems: 5,\\n items: {\\n type: \\"object\\",\\n properties: {\\n question: { type: \\"string\\", maxLength: 90 },\\n answer: { type: \\"string\\", maxLength: 300 },\\n },\\n required: [\\"question\\", \\"answer\\"],\\n },\\n },\\n },\\n required: [\\"faqs\\"],\\n },\\n `Generate the questions shoppers most plausibly have before buying the ` +\\n `product \\"${productName}\\", with accurate answers grounded in the real ` +\\n `product details and store policies. Skip any question the store data ` +\\n `can\'t answer confidently.`\\n);\\n\\nconst wrap = document.getElementById(\\"gs-faq\\");\\nfor (const { question, answer } of data.faqs) {\\n const details = document.createElement(\\"details\\");\\n const summary = document.createElement(\\"summary\\");\\n summary.textContent = question;\\n const p = document.createElement(\\"p\\");\\n p.textContent = answer;\\n details.append(summary, p);\\n wrap.append(details);\\n}\\nwrap.hidden = false;\\n```\\n\\nEngagement bonus \u2014 append a hand-off row so unanswered questions become\\nconversations:\\n\\n```js\\nconst ask = document.createElement(\\"button\\");\\nask.type = \\"button\\";\\nask.textContent = \\"Have a different question? Ask us\\";\\nask.addEventListener(\\"click\\", () =>\\n window.GreatStore.sendMessage(`I have a question about \\"${productName}\\".`)\\n);\\nwrap.append(ask);\\n```\\n\\n---\\n\\n## 4. \\"Complete the look\\" cross-sell strip\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n intro: { type: \\"string\\", maxLength: 90 },\\n picks: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n productName: { type: \\"string\\" },\\n reason: { type: \\"string\\", maxLength: 90 },\\n imageUrl: { type: \\"string\\", nullable: true },\\n productUrl: { type: \\"string\\", nullable: true },\\n },\\n required: [\\"productName\\", \\"reason\\"],\\n },\\n },\\n },\\n required: [\\"picks\\"],\\n },\\n `The shopper is viewing \\"${productName}\\". From the store\'s real catalog, ` +\\n `pick 2-4 products that genuinely pair with it and say why each one ` +\\n `completes the look or use-case. Include image and product URLs only ` +\\n `if known.`\\n);\\n\\nfor (const pick of data.picks) {\\n const card = document.createElement(\\"a\\");\\n if (pick.productUrl) card.href = pick.productUrl;\\n if (pick.imageUrl) {\\n const img = document.createElement(\\"img\\");\\n img.src = pick.imageUrl;\\n img.alt = pick.productName;\\n img.loading = \\"lazy\\";\\n card.append(img);\\n }\\n const name = document.createElement(\\"strong\\");\\n name.textContent = pick.productName;\\n const why = document.createElement(\\"p\\");\\n why.textContent = pick.reason;\\n card.append(name, why);\\n strip.append(card);\\n}\\nstrip.hidden = false;\\n```\\n\\n`imageUrl`/`productUrl` are `nullable` and optional in the render \u2014 the\\ngrounding contract means they\'re only present when the catalog actually has\\nthem. Never `require` URLs.\\n\\n---\\n\\n## 5. \\"Ask about this\\" entry points (`sendMessage` only)\\n\\nZero-generation, instant, and often the biggest engagement win per line of\\ncode. Sprinkle context-aware buttons wherever a shopper hesitates:\\n\\n```js\\nconst gs = window.GreatStore;\\nif (gs) {\\n sizeGuideLink.addEventListener(\\"click\\", (e) => {\\n e.preventDefault();\\n gs.sendMessage(`How does the sizing run on \\"${productName}\\"? I usually wear a medium.`);\\n });\\n\\n shippingRow.querySelector(\\".ask\\").addEventListener(\\"click\\", () => {\\n gs.sendMessage(`What are the shipping options and times for \\"${productName}\\"?`);\\n });\\n\\n outOfStockBadge?.addEventListener(\\"click\\", () => {\\n gs.sendMessage(`\\"${productName}\\" looks out of stock \u2014 is there anything similar in stock?`);\\n });\\n}\\n```\\n\\nWrite each message as something the shopper would plausibly say \u2014 it appears\\nin the transcript as their message.\\n\\n---\\n\\n## 6. Page-action suite (WebMCP)\\n\\nGive every conversation on the site real capabilities. Register once in a\\nshared snippet, after the SDK is ready (which guarantees\\n`document.modelContext` exists):\\n\\n```js\\nwindow.GreatStore?.ready.then(() => {\\n const text = (value) => ({\\n content: [{ type: \\"text\\", text: JSON.stringify(value) }],\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"get_cart\\",\\n description:\\n \\"Read the shopper\'s current cart on this site: items, quantities, \\" +\\n \\"and totals. Use before answering any cart question.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n async execute() {\\n return text(await (await fetch(\\"/cart.js\\")).json());\\n },\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"add_to_cart\\",\\n description:\\n \\"Add a product variant to the cart on this site. Use when the \\" +\\n \\"shopper asks to add or buy something. Confirm the variant with \\" +\\n \\"the shopper first if ambiguous.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n variantId: { type: \\"string\\" },\\n quantity: { type: \\"integer\\", minimum: 1, maximum: 10 },\\n },\\n required: [\\"variantId\\"],\\n },\\n async execute({ variantId, quantity }) {\\n if (!variantId) throw new Error(\\"variantId is required\\");\\n const res = await fetch(\\"/cart/add.js\\", {\\n method: \\"POST\\",\\n headers: { \\"Content-Type\\": \\"application/json\\" },\\n body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),\\n });\\n if (!res.ok) throw new Error(`Could not add to cart (${res.status})`);\\n document.dispatchEvent(new CustomEvent(\\"cart:refresh\\"));\\n return text(await res.json());\\n },\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"get_current_page\\",\\n description:\\n \\"Read what page the shopper is currently on, including structured \\" +\\n \\"product data when on a product page.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n execute() {\\n return text({\\n url: location.href,\\n title: document.title,\\n productJson: document.querySelector(\\"#product-json\\")?.textContent ?? null,\\n });\\n },\\n });\\n});\\n```\\n\\nPrinciples at work: throw on failure (the assistant explains and recovers),\\nreturn fresh state after mutations (the assistant confirms accurately), cap\\nquantities in the schema, and notify your own UI (`cart:refresh`) so the\\npage reflects what the AI did.\\n\\nFor a product-page-only tool, register with an `AbortSignal` and abort on\\nSPA navigation:\\n\\n```js\\nconst ac = new AbortController();\\ndocument.modelContext.registerTool(reviewsTool, { signal: ac.signal });\\nrouter.onLeave(\\"/products/:handle\\", () => ac.abort());\\n```\\n\\n---\\n\\n## 7. Campaign hero with deliberate variation\\n\\nCache-friendly variation: key the prompt to a low-cardinality period, not to\\ntime itself.\\n\\n```js\\n// ISO week number \u2192 one generation per store per week, shared by everyone.\\nconst week = (d => {\\n const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\\n t.setUTCDate(t.getUTCDate() + 4 - (t.getUTCDay() || 7));\\n return `${t.getUTCFullYear()}-W${Math.ceil((((t - Date.UTC(t.getUTCFullYear(), 0, 1)) / 864e5) + 1) / 7)}`;\\n})(new Date());\\n\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n headline: { type: \\"string\\", maxLength: 60 },\\n subline: { type: \\"string\\", maxLength: 120 },\\n featuredProductName: { type: \\"string\\", nullable: true },\\n ctaChatMessage: { type: \\"string\\", maxLength: 120 },\\n },\\n required: [\\"headline\\", \\"subline\\", \\"ctaChatMessage\\"],\\n },\\n `Variant ${week}. Write a homepage hero for this store: a headline and ` +\\n `subline spotlighting a real product or category that fits the current ` +\\n `season, plus ctaChatMessage \u2014 the first-person message a shopper ` +\\n `would send to start shopping for it.`\\n);\\n\\nheroHeadline.textContent = data.headline;\\nheroSubline.textContent = data.subline;\\nheroCta.addEventListener(\\"click\\", () =>\\n window.GreatStore.sendMessage(data.ctaChatMessage)\\n);\\nhero.hidden = false;\\n```\\n\\n---\\n\\n## 8. Custom launcher synced to panel state\\n\\nReplace the default launcher with your own UI using the lifecycle surface:\\n\\n```js\\nconst gs = window.GreatStore;\\nconst btn = document.getElementById(\\"my-chat-button\\");\\n\\ngs.ready.then(() => { btn.hidden = false; });\\nbtn.addEventListener(\\"click\\", () => gs.toggle());\\n\\ngs.on(\\"open\\", () => btn.setAttribute(\\"aria-expanded\\", \\"true\\"));\\ngs.on(\\"close\\", () => btn.setAttribute(\\"aria-expanded\\", \\"false\\"));\\n```\\n\\n`ready` resolves even when `.then()` is attached after mount, so script\\nordering doesn\'t matter. The `open`/`close` events also fire for opens the\\nSDK triggers itself (`sendMessage`, `?gs_chat=open`), keeping your button\\nstate honest.\\n","references/push-notifications.md":"# Web push re-engagement\\n\\nShoppers who opt in receive browser notifications from the store \u2014 under the\\nmerchant\'s own domain and branding, with the permission prompt shown inline\\non the merchant\'s page. Setup is two pieces: a single file hosted at the site\\nroot, and an opt-in button.\\n\\n## 1. Host `gs.js` at the site root\\n\\nDownload the store\'s loader and serve it at `/gs.js` on the merchant\'s\\ndomain:\\n\\n```\\nhttps://<slug>.greatstore.ai/gs.js \u2192 https://www.merchant-site.com/gs.js\\n```\\n\\nAlways download it from the **store\'s own subdomain** \u2014 the file is built for\\nthat store; don\'t copy one from elsewhere.\\n\\nThen load it with one tag (replacing the `embed.js` tag \u2014 `gs.js` injects the\\nembed for you and registers itself as the service worker):\\n\\n```html\\n<script src=\\"/gs.js\\"></script>\\n```\\n\\nHosting this file is what enables push. Without it, push is simply off \u2014\\n`enableNotifications()` returns `{ ok: false }` and nothing else changes.\\n\\n### Non-root hosting\\n\\nIf the platform can\'t serve files at the site root (e.g. Shopify themes\\nserve assets under a path), keep the regular `embed.js` tag and point it at\\nwhere the file lives \u2014 the path must be on the merchant\'s own origin:\\n\\n```html\\n<script\\n src=\\"https://my-store.greatstore.ai/embed.js\\"\\n data-push-sw-path=\\"/cdn/shop/files/gs.js\\"\\n></script>\\n```\\n\\n## 2. Offer the opt-in from a user gesture\\n\\n```js\\noptInButton.addEventListener(\\"click\\", async () => {\\n const { ok } = await window.GreatStore.enableNotifications();\\n optInButton.hidden = ok; // done \u2014 or quietly keep the button\\n});\\n```\\n\\nRules that make this work well:\\n\\n- **Always call it from a click** \u2014 browsers ignore or penalize permission\\n prompts that aren\'t user-initiated, and the call is designed for gesture\\n context.\\n- **Never prompt on page load.** Tie the button to a moment where\\n notifications have obvious value (\\"Notify me when this is back in stock\\",\\n post-purchase, after a chat conversation).\\n- `{ ok: false }` covers every failure the same way \u2014 unsupported browser, no\\n `gs.js` hosted, permission denied. It never rejects, and there\'s no popup\\n fallback. Design the button so a decline just leaves the page as it was;\\n don\'t show an error.\\n- The promise resolving `{ ok: true }` means this browser is subscribed.\\n There\'s nothing else to wire \u2014 notification delivery is handled by\\n GreatStore.\\n","references/structured-content.md":"# `generateStructuredContent` deep dive\\n\\nAI-generated, catalog-grounded JSON for UI you render yourself \u2014 the chat\\npanel is not involved.\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(schema, prompt);\\n```\\n\\n- `schema` \u2014 a JSON Schema describing the output (or a Zod schema exposing\\n `.toJSONSchema()`).\\n- `prompt` \u2014 what to generate.\\n- Resolves to **the generated JSON object itself**, matching the schema.\\n Rejects with an `Error` on any failure.\\n\\n## The rules that make it work well\\n\\nEach is unpacked in the sections below; this is the checklist.\\n\\n1. **Top level must be an object.** Want a list? Wrap it:\\n `{ type: \\"object\\", properties: { items: { type: \\"array\\", \u2026 } }, required: [\\"items\\"] }`.\\n2. **Steer with the prompt, not schema `description`s** \u2014 free-text schema\\n fields are stripped before the AI sees them. Use self-explanatory\\n property names (`benefitHeadline`, not `text1`).\\n3. **Only `require` what\'s guaranteed.** `required` is strictly enforced; if\\n the catalog can\'t ground a required field the whole call can fail. Require\\n structural fields, keep per-product details (image URLs, prices) optional,\\n and make rendering tolerate missing values.\\n4. **The AI can\'t see your page.** If page context matters, put it in the\\n prompt explicitly: `` `Generate care tips for the product \\"${productName}\\".` ``\\n5. **Responses are cached and shared** for up to ~24h across all visitors of\\n the same page + prompt + schema. Keep prompts deterministic per page \u2014\\n no timestamps, random values, or per-visitor data.\\n6. **Progressive enhancement, always.** Generate after the page renders into\\n a hidden container, reveal on success, leave the fallback on error. Render\\n generated strings via `textContent`, never `innerHTML`.\\n7. **One rich call beats many small ones** \u2014 there\'s a per-visitor rate limit\\n (~20 requests/minute); fetch multiple surfaces with one combined schema.\\n\\n## What actually happens\\n\\n1. The SDK posts your schema + prompt to the store\'s GreatStore endpoint,\\n along with the current **page URL and page title** (sent automatically \u2014\\n you don\'t pass them, and you can\'t override them).\\n2. GreatStore first **researches**: it looks up real data from the store\'s\\n live catalog (products, prices, availability, store info) using read-only\\n lookups. The page URL/title serve as hints about which product or category\\n to look up \u2014 they are *not* treated as a source of product data, and the\\n page\'s DOM is never read.\\n3. The AI then fills your schema from the researched data, under a strict\\n grounding contract: it must not invent product names, prices, images, IDs,\\n or descriptions. Fields it can\'t ground are omitted or `null`.\\n4. The output is validated against your schema (with internal retries) before\\n being returned and cached.\\n\\nThe request is anonymous by design \u2014 no shopper identity is attached \u2014 which\\nis what makes the response cacheable across all visitors.\\n\\n## Schema support\\n\\nTop level **must describe an object**: `type: \\"object\\"` (or a bare\\n`properties` / `anyOf`). To get a list, wrap it in an object property.\\n\\nSupported keywords (anything else is tolerated but ignored):\\n\\n- Types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`\\n- Structure: `properties`, `required`, `items`, `additionalProperties`\\n- Choice: `enum`, `const`, `anyOf`, `nullable`\\n- Constraints: `minimum`, `maximum`, `minLength`, `maxLength`, `minItems`,\\n `maxItems`, `pattern`, `format`, `default`\\n\\nValidation of the output is real: `required` is enforced, `enum`/`const`\\nmust match, numeric and length bounds are checked, and\\n`additionalProperties: false` rejects extra keys. Constraints are therefore a\\n*tool* \u2014 `maxItems: 4` reliably caps a list, `enum` reliably restricts a\\nfield \u2014 but every constraint is also a way for generation to fail, so apply\\nthem only where you\'d rather have no content than non-conforming content.\\n\\nZod schemas (or anything with a `.toJSONSchema()` method) are accepted and\\nconverted automatically.\\n\\n### Free-text schema fields are stripped\\n\\n`description`, `title`, and `example` are removed from the schema before the\\nAI sees it (they\'re a prompt-injection surface, so they\'re filtered\\nserver-side). Consequences:\\n\\n- Schema descriptions **cannot** steer generation. All steering lives in the\\n prompt string.\\n- Property *names* are the only in-schema signal of intent \u2014 make them\\n self-documenting: `ctaLabel`, `warmthRating`, `priceJustification`.\\n\\n## Prompting guide\\n\\nThe prompt is the entire instruction channel. A good prompt states, in order:\\n\\n1. **Context** \u2014 what page/situation the shopper is in, with concrete page\\n data inlined (the AI can\'t see your DOM):\\n `The shopper is viewing the product \\"Aurora Down Parka\\" on its product page.`\\n2. **Task** \u2014 what to generate, mapped loosely onto your schema\'s fields:\\n `Write a heading and 3 reasons to love it; each reason has a short title and one supporting sentence.`\\n3. **Grounding expectations** \u2014 what store data to draw on:\\n `Base every claim on the product\'s real materials, features, and price.`\\n4. **Voice** \u2014 tone and constraints:\\n `Warm and concrete. No exclamation marks, no generic marketing filler.`\\n\\nAnti-patterns:\\n\\n- **Per-visitor or per-moment data in the prompt** (names, cart contents,\\n timestamps, `Math.random()`): destroys the shared cache, so every visitor\\n pays full generation latency and the store pays for every call. If you\\n need per-shopper interaction, that\'s what `sendMessage` and the chat panel\\n are for.\\n- **Asking it to read the page** (\\"summarize the reviews shown below\\") \u2014 it\\n can\'t. Inline the data into the prompt instead, and keep what you inline\\n stable per page so caching still works.\\n- **Asking for data you should fetch yourself** (exact live stock numbers,\\n shipping ETAs). Use your own platform APIs for operational data; use\\n GreatStore for *editorial intelligence over the catalog*.\\n- **Burying instructions in schema descriptions** \u2014 stripped, see above.\\n\\n## Caching: design for it\\n\\nResponses are cached server-side for up to **24 hours**, keyed by the\\ncombination of page URL + page title + prompt + schema, and shared across all\\nvisitors. (Tracking query params like `utm_*`/`gclid` and the URL fragment\\nare ignored, so ad-tagged visits share the campaign-free page\'s cache entry.\\nMeaningful params like `?product=123` are part of the key.)\\n\\nPractical consequences:\\n\\n- **First visitor pays, the rest fly.** Expect a few seconds on a cache miss\\n and near-instant responses after. Design loading states for the miss case.\\n- **Same call on different pages = different content**, automatically \u2014 the\\n page URL is in the key and in the AI\'s hints. A single site-wide snippet\\n with a constant prompt yields per-page content for free.\\n- **Content refreshes roughly daily.** Don\'t build experiences that assume\\n minute-level freshness.\\n- **To force different content, change the prompt or schema** (e.g. a\\n campaign variant string that changes weekly \u2014 deliberate, low-cardinality\\n variation is fine; per-visitor cardinality is not).\\n\\nThe catalog research underneath is also cached briefly, so several distinct\\nsurfaces on the same page (different prompts/schemas) stay cheap even on\\ncold cache.\\n\\n## Errors and how to handle them\\n\\nThe promise rejects with `new Error(message)`. The message is\\ndeveloper-facing \u2014 never render it to shoppers. Cases:\\n\\n| Case | Message you\'ll see | Retry? |\\n|---|---|---|\\n| Bad input (empty prompt, non-object schema) | thrown immediately by the SDK | Fix the call |\\n| Invalid schema shape | `Invalid schema: \u2026` | Fix the schema |\\n| AI declined the request | `The assistant declined to generate content for this request.` | No \u2014 permanent for that prompt/schema. Rework the prompt. |\\n| Output couldn\'t satisfy the schema | `Failed to produce valid structured content` | No \u2014 usually `required`/constraints demand data the catalog lacks. Loosen the schema. |\\n| Rate limit (~20/min per visitor) | rate-limit message | Later \u2014 and consolidate calls |\\n| Network / server | varies | Next page load |\\n\\nThe uniform shopper-facing strategy: render into a hidden-by-default\\ncontainer, reveal on success, leave hidden (or show your static fallback) on\\nany rejection. One `try/catch`, no case analysis needed unless you\'re\\nlogging.\\n\\n## Performance pattern\\n\\nFire generation as early as possible without blocking render \u2014 top of your\\ndeferred script, before other work:\\n\\n```js\\nconst highlightsPromise = window.GreatStore?.generateStructuredContent\\n ? window.GreatStore.generateStructuredContent(schema, prompt).catch(() => null)\\n : Promise.resolve(null);\\n\\n// \u2026rest of page setup\u2026\\n\\nconst data = await highlightsPromise;\\nif (data) renderHighlights(data);\\n```\\n\\nThe `.catch(() => null)` attached immediately avoids unhandled-rejection\\nnoise while keeping a single render path.\\n\\nFor multiple surfaces on one page, prefer **one call with a combined\\nschema** over parallel calls \u2014 it\'s one research pass, one cache entry, and\\nno rate-limit pressure:\\n\\n```js\\nconst schema = {\\n type: \\"object\\",\\n properties: {\\n highlights: { /* \u2026 */ },\\n faq: { /* \u2026 */ },\\n crossSell: { /* \u2026 */ },\\n },\\n required: [\\"highlights\\"],\\n};\\n```\\n"}') : readTreeFromDisk(
|
|
1494
|
-
new URL(`../../skills/${SKILL_DIR_NAME}/`, import.meta.url)
|
|
1495
|
-
);
|
|
1493
|
+
cache2 = true ? JSON.parse('{"SKILL.md":"---\\nname: greatstore\\ndescription: Build AI-powered shopping experiences with GreatStore on a merchant\'s website and store. Use when installing the GreatStore chat widget on a site, generating AI content for custom UI with generateStructuredContent, adding chat entry points (sendMessage), letting the assistant act on the page via WebMCP page tools (document.modelContext.registerTool), authoring custom in-chat React components with the gs CLI, setting up web push re-engagement, or connecting AI agents to a store\'s MCP endpoints. Covers setup, schema design, caching behavior, component authoring, and ready-made recipes.\\n---\\n\\n# Building with GreatStore\\n\\nGreatStore gives a store an AI shopping assistant on two surfaces: a hosted\\nstorefront at `https://<slug>.greatstore.ai/`, and an embedded chat widget on\\nthe merchant\'s own site, installed with one script tag:\\n\\n```html\\n<script src=\\"https://my-store.greatstore.ai/embed.js\\"></script>\\n```\\n\\nEverything else a site can do with GreatStore is documented in the\\nreferences below. Read the one that matches the task before writing code \u2014\\neach facet has non-obvious rules (caching, grounding, result shapes, design\\nconstraints) that the references spell out.\\n\\n## Index\\n\\n| Goal | Use | Read |\\n|---|---|---|\\n| Install the widget; control the panel; readiness, events, troubleshooting | `window.GreatStore` SDK | [references/embed-api.md](references/embed-api.md) |\\n| AI-generated, catalog-grounded content rendered in **your own HTML/CSS** (highlights, comparisons, FAQs, gift guides) | `generateStructuredContent(schema, prompt)` | [references/structured-content.md](references/structured-content.md) |\\n| Copy-paste on-page experiences | recipes built on the SDK | the [Recipes](#recipes) table below |\\n| Contextual **conversation entry points** anywhere on the page | `sendMessage(text)`, `open()`, `?gs_chat=open` | [references/embed-api.md](references/embed-api.md) |\\n| Let the assistant **act on the page** (cart, navigation, filters) | WebMCP: `document.modelContext.registerTool(...)` | [references/embed-api.md](references/embed-api.md) |\\n| Custom **interactive UI inside the chat** (configurators, quizzes, size guides, booking forms) | remote components shipped with the `gs` CLI | [references/chat-components.md](references/chat-components.md) |\\n| **Re-engage shoppers** with browser notifications | merchant-hosted `gs.js` + `enableNotifications()` | [references/push-notifications.md](references/push-notifications.md) |\\n| Connect **AI agents** to the store (shopping tools over MCP, CLI docs for coding agents) | the store\'s MCP endpoints | [references/agents-and-mcp.md](references/agents-and-mcp.md) |\\n\\n## Three things to know before any of it\\n\\n- **Every code sample in this skill is a reference implementation, not a\\n drop-in.** Samples are framework-free vanilla JS so they stay portable \u2014\\n re-express the same logic in the conventions of the repo you\'re working\\n in (React, Vue, Shopify Liquid sections, Svelte, \u2026); never retrofit the\\n sample as-is into a codebase with its own framework.\\n- The page\'s domain **must be in the store\'s allowed domains** (GreatStore\\n store settings). If it isn\'t, nothing works and the console shows\\n `[GreatStore] Chat is unavailable on <origin>\u2026` \u2014 check this first whenever\\n the embed appears dead.\\n- Every SDK call is safe immediately after the script tag \u2014 pre-mount calls\\n queue and replay in order, and the SDK pre-warms itself in the background.\\n\\n## Recipes\\n\\nComplete, framework-free implementations, one per file. Shared conventions:\\ncontainers start `hidden` and reveal only on success (a failed generation\\nchanges nothing); generated strings render via `textContent`, never\\n`innerHTML`; every recipe guards on `window.GreatStore`; prompts stay\\ndeterministic per page so every visitor after the first hits the cache.\\nInline real product/page data into prompts where the platform exposes it.\\n\\n| Recipe | What it builds | Use it for |\\n|---|---|---|\\n| [GreatStore launchers](recipes/launchers.md) | Horizontally scrollable AI-generated chips \u2014 engaging first-person questions about the current page; tap to ask the assistant. | Instant engagement on any page type \u2014 product, collection, blog, home. Simple yet effective; start here. |\\n| [Ask-about-this entry points](recipes/ask-about-this.md) | One-line `sendMessage` buttons wired to existing page elements. | Size guides, shipping rows, out-of-stock badges \u2014 anywhere a shopper hesitates. |\\n| [Product FAQ accordion](recipes/product-faq.md) | Grounded pre-purchase Q&A with an \\"ask us\\" handoff into chat. | Product pages; answering objections before they cost the sale. |\\n| [Comparison table](recipes/comparison-table.md) | AI-picked representative products compared on category-relevant criteria. | Collection pages where shoppers weigh options. |\\n| [Complete the look](recipes/complete-the-look.md) | Catalog-grounded cross-sell strip with a reason per pick. | Product pages; raising order value with genuine pairings. |\\n| [Campaign hero](recipes/campaign-hero.md) | Seasonal homepage hero copy, cache-keyed to the ISO week. | Fresh homepage/campaign copy without manual rewrites. |\\n| [Gift finder funnel](recipes/gift-finder-funnel.md) | Quiz teaser \u2192 chat handoff \u2192 page-tool navigation; the full funnel. | Gifting seasons, guided discovery, homepage engagement. |\\n| [Page-action suite](recipes/page-action-suite.md) | WebMCP cart/page tools every conversation can use. | Any site where the assistant should act, not just advise. |\\n| [Custom chat button](recipes/custom-chat-button.md) | Branded launcher synced via `ready` + `open`/`close` events. | Replacing the default launcher with the site\'s own UI. |\\n\\n## How the facets combine\\n\\nThe strongest pattern is the **teaser \u2192 conversation \u2192 action** funnel:\\n`generateStructuredContent` renders a grounded teaser in the merchant\'s\\ndesign; each option\'s click handler calls `sendMessage` with the shopper\'s\\nchoice, dropping them into a conversation with momentum; WebMCP page tools\\nand custom chat components let that conversation actually do things \u2014 add to\\ncart, configure a product, book a slot \u2014 so it ends in a conversion, not a\\ncopy-paste. The [gift finder funnel](recipes/gift-finder-funnel.md)\\nrecipe is this funnel end to end.\\n","recipes/ask-about-this.md":"# \\"Ask about this\\" entry points (`sendMessage` only)\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nZero-generation, instant, and often the biggest engagement win per line of\\ncode. Sprinkle context-aware buttons wherever a shopper hesitates:\\n\\n```js\\nconst gs = window.GreatStore;\\nif (gs) {\\n sizeGuideLink.addEventListener(\\"click\\", (e) => {\\n e.preventDefault();\\n gs.sendMessage(`How does the sizing run on \\"${productName}\\"? I usually wear a medium.`);\\n });\\n\\n shippingRow.querySelector(\\".ask\\").addEventListener(\\"click\\", () => {\\n gs.sendMessage(`What are the shipping options and times for \\"${productName}\\"?`);\\n });\\n\\n outOfStockBadge?.addEventListener(\\"click\\", () => {\\n gs.sendMessage(`\\"${productName}\\" looks out of stock \u2014 is there anything similar in stock?`);\\n });\\n}\\n```\\n\\nWrite each message as something the shopper would plausibly say \u2014 it appears\\nin the transcript as their message.\\n","recipes/campaign-hero.md":"# Campaign hero with deliberate variation\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nCache-friendly variation: key the prompt to a low-cardinality period, not to\\ntime itself.\\n\\n```js\\n// ISO week number \u2192 one generation per store per week, shared by everyone.\\nconst week = (d => {\\n const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\\n t.setUTCDate(t.getUTCDate() + 4 - (t.getUTCDay() || 7));\\n return `${t.getUTCFullYear()}-W${Math.ceil((((t - Date.UTC(t.getUTCFullYear(), 0, 1)) / 864e5) + 1) / 7)}`;\\n})(new Date());\\n\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n headline: { type: \\"string\\", maxLength: 60 },\\n subline: { type: \\"string\\", maxLength: 120 },\\n featuredProductName: { type: \\"string\\", nullable: true },\\n ctaChatMessage: { type: \\"string\\", maxLength: 120 },\\n },\\n required: [\\"headline\\", \\"subline\\", \\"ctaChatMessage\\"],\\n },\\n `Variant ${week}. Write a homepage hero for this store: a headline and ` +\\n `subline spotlighting a real product or category that fits the current ` +\\n `season, plus ctaChatMessage \u2014 the first-person message a shopper ` +\\n `would send to start shopping for it.`\\n);\\n\\nheroHeadline.textContent = data.headline;\\nheroSubline.textContent = data.subline;\\nheroCta.addEventListener(\\"click\\", () =>\\n window.GreatStore.sendMessage(data.ctaChatMessage)\\n);\\nhero.hidden = false;\\n```\\n","recipes/comparison-table.md":"# Collection-page comparison table\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\n```html\\n<section id=\\"gs-compare\\" hidden>\\n <h3>Quick comparison</h3>\\n <table><thead id=\\"gsc-head\\"></thead><tbody id=\\"gsc-body\\"></tbody></table>\\n</section>\\n\\n<script>\\n (async () => {\\n if (!window.GreatStore?.generateStructuredContent) return;\\n const collection = \\"winter jackets\\"; // \u2190 your collection name\\n try {\\n const data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n criteria: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: { type: \\"string\\", maxLength: 25 },\\n },\\n rows: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n productName: { type: \\"string\\" },\\n bestFor: { type: \\"string\\", maxLength: 60 },\\n values: {\\n type: \\"array\\",\\n items: { type: \\"string\\", maxLength: 60 },\\n },\\n },\\n required: [\\"productName\\", \\"bestFor\\", \\"values\\"],\\n },\\n },\\n },\\n required: [\\"criteria\\", \\"rows\\"],\\n },\\n `The shopper is browsing the \\"${collection}\\" collection. Pick the ` +\\n `3-4 most representative products and compare them. Choose the ` +\\n `criteria a shopper actually decides on for this category. ` +\\n `\\"values\\" must align with \\"criteria\\" by index. Add a one-line ` +\\n `\\"bestFor\\" verdict per product. Only use real products and facts.`\\n );\\n\\n const head = document.getElementById(\\"gsc-head\\");\\n const hr = document.createElement(\\"tr\\");\\n for (const h of [\\"Product\\", ...data.criteria, \\"Best for\\"]) {\\n const th = document.createElement(\\"th\\");\\n th.textContent = h;\\n hr.append(th);\\n }\\n head.append(hr);\\n\\n const body = document.getElementById(\\"gsc-body\\");\\n for (const row of data.rows) {\\n const tr = document.createElement(\\"tr\\");\\n const cells = [row.productName, ...(row.values ?? []), row.bestFor];\\n for (let i = 0; i < data.criteria.length + 2; i++) {\\n const td = document.createElement(\\"td\\");\\n td.textContent = cells[i] ?? \\"\u2014\\";\\n tr.append(td);\\n }\\n body.append(tr);\\n }\\n document.getElementById(\\"gs-compare\\").hidden = false;\\n } catch {}\\n })();\\n</script>\\n```\\n\\nNote the index-aligned `values`/`criteria` trick and the `?? \\"\u2014\\"` guard \u2014\\ngrounding means a value the catalog can\'t support may be missing.\\n","recipes/complete-the-look.md":"# \\"Complete the look\\" cross-sell strip\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n intro: { type: \\"string\\", maxLength: 90 },\\n picks: {\\n type: \\"array\\", minItems: 2, maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n productName: { type: \\"string\\" },\\n reason: { type: \\"string\\", maxLength: 90 },\\n imageUrl: { type: \\"string\\", nullable: true },\\n productUrl: { type: \\"string\\", nullable: true },\\n },\\n required: [\\"productName\\", \\"reason\\"],\\n },\\n },\\n },\\n required: [\\"picks\\"],\\n },\\n `The shopper is viewing \\"${productName}\\". From the store\'s real catalog, ` +\\n `pick 2-4 products that genuinely pair with it and say why each one ` +\\n `completes the look or use-case. Include image and product URLs only ` +\\n `if known.`\\n);\\n\\nfor (const pick of data.picks) {\\n const card = document.createElement(\\"a\\");\\n if (pick.productUrl) card.href = pick.productUrl;\\n if (pick.imageUrl) {\\n const img = document.createElement(\\"img\\");\\n img.src = pick.imageUrl;\\n img.alt = pick.productName;\\n img.loading = \\"lazy\\";\\n card.append(img);\\n }\\n const name = document.createElement(\\"strong\\");\\n name.textContent = pick.productName;\\n const why = document.createElement(\\"p\\");\\n why.textContent = pick.reason;\\n card.append(name, why);\\n strip.append(card);\\n}\\nstrip.hidden = false;\\n```\\n\\n`imageUrl`/`productUrl` are `nullable` and optional in the render \u2014 the\\ngrounding contract means they\'re only present when the catalog actually has\\nthem. Never `require` URLs.\\n","recipes/custom-chat-button.md":"# Custom chat button synced to panel state\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nReplace the default launcher with your own UI using the lifecycle surface:\\n\\n```js\\nconst gs = window.GreatStore;\\nconst btn = document.getElementById(\\"my-chat-button\\");\\n\\ngs.ready.then(() => { btn.hidden = false; });\\nbtn.addEventListener(\\"click\\", () => gs.toggle());\\n\\ngs.on(\\"open\\", () => btn.setAttribute(\\"aria-expanded\\", \\"true\\"));\\ngs.on(\\"close\\", () => btn.setAttribute(\\"aria-expanded\\", \\"false\\"));\\n```\\n\\n`ready` resolves even when `.then()` is attached after mount, so script\\nordering doesn\'t matter. The `open`/`close` events also fire for opens the\\nSDK triggers itself (`sendMessage`, `?gs_chat=open`), keeping your button\\nstate honest.\\n","recipes/gift-finder-funnel.md":"# Gift finder funnel (teaser \u2192 conversation \u2192 action)\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nThe flagship pattern: an AI-generated quiz card in your design, whose answers\\ndrop the shopper into a chat that can act on the page.\\n\\n```html\\n<section id=\\"gift-finder\\" hidden>\\n <h3 id=\\"gf-question\\"></h3>\\n <div id=\\"gf-options\\"></div>\\n</section>\\n\\n<script>\\n (async () => {\\n const gs = window.GreatStore;\\n if (!gs?.generateStructuredContent) return;\\n\\n // Tools the resulting conversation can use. Registered once the\\n // SDK is ready (so document.modelContext exists); the assistant\\n // discovers them on its next turn automatically.\\n gs.ready.then(() => {\\n document.modelContext.registerTool({\\n name: \\"go_to_product\\",\\n description:\\n \\"Navigate the shopper to a product page on this site. Use when \\" +\\n \\"the shopper picks a product they want to see.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: { url: { type: \\"string\\" } },\\n required: [\\"url\\"],\\n },\\n execute({ url }) {\\n const u = new URL(String(url), location.origin);\\n if (u.origin !== location.origin) throw new Error(\\"Only same-site URLs allowed\\");\\n location.assign(u.href);\\n return { content: [{ type: \\"text\\", text: \\"Navigating.\\" }] };\\n },\\n });\\n });\\n\\n try {\\n const data = await gs.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n question: { type: \\"string\\", maxLength: 80 },\\n options: {\\n type: \\"array\\",\\n minItems: 3,\\n maxItems: 4,\\n items: {\\n type: \\"object\\",\\n properties: {\\n label: { type: \\"string\\", maxLength: 30 },\\n chatMessage: { type: \\"string\\", maxLength: 140 },\\n },\\n required: [\\"label\\", \\"chatMessage\\"],\\n },\\n },\\n },\\n required: [\\"question\\", \\"options\\"],\\n },\\n \\"Create one engaging gift-finder opening question for this store, \\" +\\n \\"with 3-4 answer options that map to real areas of the catalog. \\" +\\n \\"For each option also write chatMessage: the message a shopper \\" +\\n \\"would send to a shopping assistant after picking it, phrased in \\" +\\n \\"first person (e.g. \\\\\\"I\'m shopping for my dad who loves hiking\\\\\\").\\"\\n );\\n\\n document.getElementById(\\"gf-question\\").textContent = data.question;\\n const wrap = document.getElementById(\\"gf-options\\");\\n for (const opt of data.options) {\\n const btn = document.createElement(\\"button\\");\\n btn.type = \\"button\\";\\n btn.textContent = opt.label;\\n btn.addEventListener(\\"click\\", () => gs.sendMessage(opt.chatMessage));\\n wrap.append(btn);\\n }\\n document.getElementById(\\"gift-finder\\").hidden = false;\\n } catch {}\\n })();\\n</script>\\n```\\n\\nWhy it works: the teaser costs one cached generation for all visitors, each\\nclick opens a conversation that already has direction, and `go_to_product`\\nlets the conversation end on a product page instead of in a dead end.\\n","recipes/launchers.md":"# GreatStore launchers \u2014 AI question chips\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nA horizontally scrollable row of chips, each a highly engaging first-person\\nquestion about the current page. Tapping a chip sends that question to the\\nassistant \u2014 `generateStructuredContent` writes the questions, `sendMessage`\\nfires them. Simple yet effective: it works on every page type, costs one\\ncached generation per page, and every tap starts a conversation that already\\nhas a great opening line.\\n\\n```html\\n<div id=\\"gs-launchers\\" hidden></div>\\n\\n<style>\\n #gs-launchers {\\n display: flex;\\n gap: 0.5em;\\n overflow-x: auto;\\n -webkit-overflow-scrolling: touch;\\n scrollbar-width: none;\\n padding: 0.5em 1em;\\n }\\n #gs-launchers::-webkit-scrollbar { display: none; }\\n #gs-launchers button {\\n flex: 0 0 auto;\\n white-space: nowrap;\\n border: 1px solid #ddd;\\n border-radius: 999px;\\n padding: 0.5em 0.9em;\\n background: #fff;\\n cursor: pointer;\\n }\\n</style>\\n\\n<script>\\n (async () => {\\n const gs = window.GreatStore;\\n if (!gs?.generateStructuredContent) return;\\n try {\\n const data = await gs.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n launchers: {\\n type: \\"array\\", minItems: 4, maxItems: 6,\\n items: {\\n type: \\"object\\",\\n properties: {\\n chip: { type: \\"string\\", maxLength: 32 },\\n question: { type: \\"string\\", maxLength: 140 },\\n },\\n required: [\\"chip\\", \\"question\\"],\\n },\\n },\\n },\\n required: [\\"launchers\\"],\\n },\\n `The shopper is on the page \\"${document.title}\\". Write 4-6 launcher ` +\\n `chips for a shopping assistant. For each, \\"question\\" is a highly ` +\\n `engaging first-person question this shopper would genuinely want ` +\\n `answered on this page \u2014 specific to its product, category, or ` +\\n `content, never generic \u2014 and \\"chip\\" is a 2-4 word teaser of it. ` +\\n `Vary the angles: fit and use, comparisons, gifting, care, what\'s ` +\\n `popular.`\\n );\\n\\n const row = document.getElementById(\\"gs-launchers\\");\\n for (const { chip, question } of data.launchers) {\\n const btn = document.createElement(\\"button\\");\\n btn.type = \\"button\\";\\n btn.textContent = chip;\\n btn.title = question;\\n btn.addEventListener(\\"click\\", () => gs.sendMessage(question));\\n row.append(btn);\\n }\\n row.hidden = false;\\n } catch {}\\n })();\\n</script>\\n```\\n\\nWhy it works:\\n\\n- **The chip is the teaser, the question is the payload.** A 2-4 word chip\\n scans instantly; the full first-person question lands in the transcript\\n reading like something the shopper typed, and gives the assistant a\\n well-formed prompt. The `title` attribute previews the full question on\\n hover.\\n- **Per-page for free.** The page URL is part of the generation context and\\n the cache key, so one site-wide snippet yields different chips on every\\n page \u2014 each cached for all visitors.\\n- **Placement is the lever.** Under the product title, above the grid on\\n collections, at the end of a blog post \u2014 wherever a shopper pauses to\\n wonder, the chips name the question for them.\\n\\nTips:\\n\\n- Inline the product or collection name into the prompt when the platform\\n exposes it \u2014 it beats relying on `document.title`.\\n- Restyle the chips to the site\'s design system; the CSS above is just the\\n scroll mechanics (flex row, `overflow-x: auto`, hidden scrollbars,\\n `white-space: nowrap`).\\n- Resist adding more than ~6 chips \u2014 a launcher row is an invitation, not a\\n sitemap.\\n","recipes/page-action-suite.md":"# Page-action suite (WebMCP)\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\nGive every conversation on the site real capabilities. Register once in a\\nshared snippet, after the SDK is ready (which guarantees\\n`document.modelContext` exists):\\n\\n```js\\nwindow.GreatStore?.ready.then(() => {\\n const text = (value) => ({\\n content: [{ type: \\"text\\", text: JSON.stringify(value) }],\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"get_cart\\",\\n description:\\n \\"Read the shopper\'s current cart on this site: items, quantities, \\" +\\n \\"and totals. Use before answering any cart question.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n async execute() {\\n return text(await (await fetch(\\"/cart.js\\")).json());\\n },\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"add_to_cart\\",\\n description:\\n \\"Add a product variant to the cart on this site. Use when the \\" +\\n \\"shopper asks to add or buy something. Confirm the variant with \\" +\\n \\"the shopper first if ambiguous.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n variantId: { type: \\"string\\" },\\n quantity: { type: \\"integer\\", minimum: 1, maximum: 10 },\\n },\\n required: [\\"variantId\\"],\\n },\\n async execute({ variantId, quantity }) {\\n if (!variantId) throw new Error(\\"variantId is required\\");\\n const res = await fetch(\\"/cart/add.js\\", {\\n method: \\"POST\\",\\n headers: { \\"Content-Type\\": \\"application/json\\" },\\n body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),\\n });\\n if (!res.ok) throw new Error(`Could not add to cart (${res.status})`);\\n document.dispatchEvent(new CustomEvent(\\"cart:refresh\\"));\\n return text(await res.json());\\n },\\n });\\n\\n document.modelContext.registerTool({\\n name: \\"get_current_page\\",\\n description:\\n \\"Read what page the shopper is currently on, including structured \\" +\\n \\"product data when on a product page.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n execute() {\\n return text({\\n url: location.href,\\n title: document.title,\\n productJson: document.querySelector(\\"#product-json\\")?.textContent ?? null,\\n });\\n },\\n });\\n});\\n```\\n\\nPrinciples at work: throw on failure (the assistant explains and recovers),\\nreturn fresh state after mutations (the assistant confirms accurately), cap\\nquantities in the schema, and notify your own UI (`cart:refresh`) so the\\npage reflects what the AI did.\\n\\nFor a product-page-only tool, register with an `AbortSignal` and abort on\\nSPA navigation:\\n\\n```js\\nconst ac = new AbortController();\\ndocument.modelContext.registerTool(reviewsTool, { signal: ac.signal });\\nrouter.onLeave(\\"/products/:handle\\", () => ac.abort());\\n```\\n","recipes/product-faq.md":"# Product FAQ accordion\\n\\n> **Treat this code as a reference, not a drop-in.** Samples are\\n> framework-free vanilla JS so they stay portable \u2014 re-express the same\\n> logic in the conventions of the repo you\'re working in (React, Vue,\\n> Shopify Liquid sections, Svelte, \u2026) instead of retrofitting the sample\\n> as-is.\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(\\n {\\n type: \\"object\\",\\n properties: {\\n faqs: {\\n type: \\"array\\", minItems: 3, maxItems: 5,\\n items: {\\n type: \\"object\\",\\n properties: {\\n question: { type: \\"string\\", maxLength: 90 },\\n answer: { type: \\"string\\", maxLength: 300 },\\n },\\n required: [\\"question\\", \\"answer\\"],\\n },\\n },\\n },\\n required: [\\"faqs\\"],\\n },\\n `Generate the questions shoppers most plausibly have before buying the ` +\\n `product \\"${productName}\\", with accurate answers grounded in the real ` +\\n `product details and store policies. Skip any question the store data ` +\\n `can\'t answer confidently.`\\n);\\n\\nconst wrap = document.getElementById(\\"gs-faq\\");\\nfor (const { question, answer } of data.faqs) {\\n const details = document.createElement(\\"details\\");\\n const summary = document.createElement(\\"summary\\");\\n summary.textContent = question;\\n const p = document.createElement(\\"p\\");\\n p.textContent = answer;\\n details.append(summary, p);\\n wrap.append(details);\\n}\\nwrap.hidden = false;\\n```\\n\\nEngagement bonus \u2014 append a hand-off row so unanswered questions become\\nconversations:\\n\\n```js\\nconst ask = document.createElement(\\"button\\");\\nask.type = \\"button\\";\\nask.textContent = \\"Have a different question? Ask us\\";\\nask.addEventListener(\\"click\\", () =>\\n window.GreatStore.sendMessage(`I have a question about \\"${productName}\\".`)\\n);\\nwrap.append(ask);\\n```\\n","references/agents-and-mcp.md":"# AI agents and the store\'s MCP endpoints\\n\\nBeyond the widget, every GreatStore store is reachable by AI agents directly\\nover the Model Context Protocol. Three URLs, all on the store\'s subdomain,\\nnone requiring authentication:\\n\\n| URL | What it is |\\n|---|---|\\n| `https://<slug>.greatstore.ai/.well-known/mcp/server-card.json` | Standard MCP server card \u2014 machine-readable discovery document for the store. |\\n| `https://<slug>.greatstore.ai/mcp` | The store\'s **shopping tools** over MCP \u2014 product search and related catalog actions, the same capabilities the assistant itself shops with. |\\n| `https://<slug>.greatstore.ai/admin-mcp` | A **documentation server for coding agents**: its tools return usage docs for the `gs` CLI (login, init, build, push, publish). |\\n\\n## Shopping tools \u2014 `/mcp`\\n\\nConnect any MCP-capable agent to let it browse and shop the store\\nprogrammatically. This is the integration point for agentic-shopping\\nclients, comparison bots, or the merchant\'s own automations that need live\\ncatalog answers.\\n\\n## CLI docs for coding agents \u2014 `/admin-mcp`\\n\\nA stateless HTTP MCP whose tools hand back markdown documentation for `gs`\\nCLI commands. When accessed via the store\'s subdomain, every example comes\\npre-pinned to that store\'s slug, so the agent never has to ask which store\\nto target.\\n\\n```\\nclaude mcp add --transport http greatstore https://my-store.greatstore.ai/admin-mcp\\n```\\n\\nUseful when a coding agent is shipping chat components\\n([chat-components.md](chat-components.md)) and needs the exact command for\\nthe next step. If this skill is installed, the agent already has the\\nworkflow \u2014 the MCP is the self-serve alternative for agents that don\'t.\\n\\n## What to use when\\n\\n- **Building the merchant\'s site** \u2192 this skill\'s other references (the SDK,\\n structured content, page tools).\\n- **A coding agent shipping chat components** \u2192 the `gs` CLI, with\\n `/admin-mcp` as its built-in documentation.\\n- **An agent that needs to *shop* the store** (search products, read catalog\\n data) from outside any web page \u2192 `/mcp`.\\n","references/chat-components.md":"# Custom chat components \u2014 authoring with the `gs` CLI\\n\\nRemote components are React components the assistant renders **inside the\\nconversation** \u2014 product configurators, quizzes, size guides, booking forms,\\nanything richer than text. Each component is an AI-callable tool: the\\nmanifest\'s `description` tells the assistant *when* to show it, its\\n`inputSchema` declares the props the assistant fills in, and a `displayMode`\\npicks where it appears.\\n\\nAuthoring requires store-owner access (`gs login` signs in with the store\\nowner\'s account).\\n\\n## Workflow\\n\\n```\\nnpm install -g @greatstore/cli # or npx @greatstore/cli <command>\\ngs login # browser sign-in\\ngs init --store my-store # scaffold a project root\\ncd <project> && npm install\\ngs init size_guide # scaffold components/size_guide/\\n# \u2026 edit components/size_guide/{component.tsx,manifest.json} \u2026\\ngs build # bundle every component\\ngs push # upload changed components as drafts\\ngs publish size_guide # promote to live\\n```\\n\\n`gs list` shows what\'s deployed (with dashboard links); `gs pull` round-trips\\nremote components back to disk. `gs push` hashes components and only uploads\\nwhat changed.\\n\\nThe scaffold writes an `AGENTS.md` into the project (with `CLAUDE.md` /\\n`GEMINI.md` symlinked) containing the complete design rules and brand\\nvariable table \u2014 your coding agent picks it up automatically when working in\\nthe project. The store\'s `/admin-mcp` endpoint serves the same CLI docs to\\nagents over MCP (see [agents-and-mcp.md](agents-and-mcp.md)).\\n\\n## `manifest.json`\\n\\n```json\\n{\\n \\"name\\": \\"size_guide\\",\\n \\"displayName\\": \\"Size guide\\",\\n \\"description\\": \\"Interactive size guide. Show when the shopper asks about sizing or fit for apparel.\\",\\n \\"displayMode\\": \\"inline\\",\\n \\"inputSchema\\": {\\n \\"type\\": \\"object\\",\\n \\"properties\\": {\\n \\"productName\\": { \\"type\\": \\"string\\" },\\n \\"category\\": { \\"type\\": \\"string\\" }\\n },\\n \\"required\\": [\\"productName\\"]\\n }\\n}\\n```\\n\\n| Field | Meaning |\\n|---|---|\\n| `name` | Tool name, snake_case (`^[a-z][a-z0-9_]*$`), matches the folder under `components/`. |\\n| `displayName` | Friendly label shown in chat UI. |\\n| `description` | **Load-bearing** \u2014 how the assistant decides when to render the component. Say what it shows *and* when to use it, like any good tool description. |\\n| `displayMode` | Where it renders \u2014 see below. |\\n| `inputSchema` | JSON Schema for the props the assistant fills. Keep it tight; required fields the AI can\'t infer cause bad calls. |\\n| `async` | Set `true` for backend-backed components (see Async below). |\\n\\n### Display modes\\n\\n- `inline` \u2014 a bubble inside the chat transcript; persists with the message\\n log.\\n- `over-input` \u2014 floats above the chat input (like a question overlay);\\n cleared by the next user turn or an explicit close.\\n- `fullscreen` \u2014 takes over the full preview surface; persists until the\\n next widget-emitting tool call or an explicit close.\\n\\n## The component contract\\n\\n`component.tsx` default-exports a React component. Its props are the\\n`inputSchema` fields the assistant filled, plus three GreatStore-injected\\nlifecycle props (always present):\\n\\n| Prop | What it does |\\n|---|---|\\n| `onSendMessage(text)` | Send text into the chat as if the shopper typed it \u2014 lets the component drive the conversation (\\"Selected size M, what\'s the return policy?\\"). |\\n| `onCallTool(name, args)` | Chain into another remote-component tool by name. |\\n| `onClose()` | Dismiss the host slot. `over-input` clears the overlay, `fullscreen` reverts the pane, `inline` is a no-op. |\\n\\n```tsx\\nimport React from \\"react\\";\\n\\ninterface Props {\\n productName: string;\\n category?: string;\\n onSendMessage: (text: string) => void;\\n onCallTool: (name: string, args: Record<string, unknown>) => void;\\n onClose: () => void;\\n}\\n\\nexport default function SizeGuide({ productName, onSendMessage }: Props) {\\n return (\\n <div\\n style={{\\n padding: \\"1em\\",\\n border: \\"1px solid var(--color-border-default)\\",\\n borderRadius: \\"var(--radius-lg)\\",\\n background: \\"var(--color-surface)\\",\\n color: \\"var(--color-foreground)\\",\\n fontFamily: \\"var(--font-sans)\\",\\n }}\\n >\\n {/* \u2026 sizes for {productName} \u2026 */}\\n <button onClick={() => onSendMessage(`Size M of \\"${productName}\\" \u2014 is it in stock?`)}>\\n Check size M\\n </button>\\n </div>\\n );\\n}\\n```\\n\\n## Design rules (non-negotiable)\\n\\nComponents render inside arbitrary publisher pages *and* the GreatStore\\nstorefront; you control neither the host\'s root font size nor its colors.\\n\\n1. **Size in `em`, never `rem`** \u2014 `rem` resolves against the host page\'s\\n root font size, which is arbitrary (`html { font-size: 8px }` breaks every\\n `rem` dimension). `em` stays self-consistent anywhere. Borders may stay\\n `px`.\\n2. **Never hardcode colors, fonts, or radii** \u2014 read the brand CSS variables\\n GreatStore injects (`--color-primary`, `--color-surface`,\\n `--color-foreground`, `--color-border-default`, `--font-sans`,\\n `--radius-lg`, \u2026) so the component restyles itself with the store\'s\\n theme. The scaffolded `AGENTS.md` has the full variable table.\\n\\n## Async components (backend-backed data)\\n\\nIf a component must load data before it can render correctly, don\'t render a\\nshell and fetch in `useEffect` \u2014 set `\\"async\\": true` in the manifest and\\nexport an **async** default. GreatStore shows its own loading state, awaits\\nyour promise, and renders what it resolves to.\\n\\nA thrown error is a **retry signal**: the assistant sees it and usually\\nre-calls the tool. So only throw when a *different* call could help:\\n\\n1. Validate the AI-passed props first and throw on bad input \u2014 the AI can\\n fix the args and retry. (Don\'t validate the API\'s *output* and throw: the\\n AI can\'t fix your backend, it\'ll just loop.)\\n2. Throw on failures where retrying differently could succeed, and say what\\n to change (e.g. empty search \u2192 `\\"no results for X \u2014 try a broader keyword\\"`).\\n3. For idempotent failures (500, timeout, missing record) render a graceful\\n fallback instead of throwing \u2014 re-running the same call changes nothing.\\n\\n```tsx\\nexport default async function Results(props: Props) {\\n if (!props.query?.trim()) throw new Error(\\"missing required prop: query\\");\\n const res = await fetch(`/api/search?q=${encodeURIComponent(props.query)}`);\\n if (res.ok) {\\n const { results } = await res.json();\\n if (results.length === 0)\\n throw new Error(`no results for \\"${props.query}\\" \u2014 try a broader keyword`);\\n return <ul>{/* render results */}</ul>;\\n }\\n return <p>Couldn\'t load results right now.</p>; // idempotent: don\'t throw\\n}\\n```\\n\\n## When to build a component vs. the other facets\\n\\n- Content for the **merchant\'s page** \u2192 `generateStructuredContent`\\n ([structured-content.md](structured-content.md)).\\n- Letting the assistant **act on the page** \u2192 WebMCP page tools\\n ([embed-api.md](embed-api.md)).\\n- Rich, interactive UI **inside the conversation itself**, available on the\\n storefront and every embed without page changes \u2192 a chat component.\\n","references/embed-api.md":"# `window.GreatStore` API reference\\n\\n## Setup\\n\\n```html\\n<script src=\\"https://my-store.greatstore.ai/embed.js\\"></script>\\n```\\n\\nOne script tag, anywhere on the page (end of `<body>` preferred), with the\\nstore\'s slug in the host. The `window.GreatStore` object exists synchronously\\nonce the script executes; every method below is safe to call before the chat\\nUI has finished loading \u2014 pre-mount calls are queued and replayed in order\\nonce it mounts. The SDK pre-warms its chat bundle in the background\\nautomatically; the panel stays closed until `open()` / `toggle()` /\\n`sendMessage()` is called or the shopper clicks the launcher.\\n\\nRequirements:\\n\\n- The page\'s domain must be in the store\'s **allowed domains** (GreatStore\\n store settings) \u2014 see Troubleshooting below for the failure signature.\\n- If the store also wants push notifications, host `gs.js` at the site root\\n and load that instead of `embed.js` \u2014 it injects the embed for you. See\\n [push-notifications.md](push-notifications.md).\\n\\nCode samples throughout are framework-free reference implementations \u2014\\nre-express them in the host repo\'s framework (React, Vue, Shopify Liquid,\\n\u2026) rather than retrofitting them as-is.\\n\\n## Properties\\n\\n| Property | Type | Description |\\n|---|---|---|\\n| `slug` | `string` | The store identifier the script was loaded for. |\\n| `host` | `string` | `\\"greatstore.ai\\"`. |\\n| `embedHost` | `string` | Origin the embed assets load from, e.g. `https://<slug>.greatstore.ai`. |\\n| `ready` | `Promise<void>` | Resolves when the chat UI has mounted and `open()` would render instantly. Resolved promises replay, so `.then()` works no matter when it\'s attached. The recommended readiness signal. |\\n| `isReady` | `boolean` | Synchronous alternative to `ready`. `false` until mount. |\\n\\nA `greatstore:ready` `CustomEvent` (with the SDK object as `detail`) is also\\ndispatched on `window` at the moment `ready` resolves, for declarative\\ntooling. Unlike the promise, the listener must be attached before mount\\ncompletes \u2014 attach it before (or immediately after) the embed script tag.\\n\\n## Methods\\n\\n### `load(): void`\\n\\nPre-warms the chat bundle and identity in the background without opening the\\npanel. Called automatically when `embed.js` runs, so you rarely need it.\\nIdempotent.\\n\\n### `open(): void` / `close(): void` / `toggle(): void`\\n\\nOpen, close, or toggle the chat panel. On desktop the panel is a floating\\nside panel; under 768px viewport width it\'s a full-height drawer. All three\\nqueue if called before mount.\\n\\n### `sendMessage(text: string): void`\\n\\nSends `text` as the shopper\'s own visible chat message and **opens the panel\\nif it\'s closed**. The text is trimmed; empty or whitespace-only strings are\\nsilently dropped. Queues if called before mount.\\n\\nThis is the highest-leverage one-liner in the SDK: any element on the page\\ncan become a conversation entry point with context baked into the question.\\n\\n```js\\ndocument.querySelector(\\"#ask-fit\\").addEventListener(\\"click\\", () => {\\n window.GreatStore.sendMessage(\\n `I\'m looking at \\"${productName}\\" \u2014 how does the sizing run?`\\n );\\n});\\n```\\n\\nBecause the message renders as if the shopper typed it, write it in the\\nshopper\'s voice. It is not a hidden-context channel \u2014 don\'t stuff it with\\ninvisible instructions or data dumps.\\n\\n### `on(event: string, handler: (...args) => void): () => void`\\n\\nSubscribe to SDK events. Returns an unsubscribe function. Listeners attached\\nbefore mount are queued and wired up at mount. Handler exceptions are caught\\nand reported \u2014 they won\'t break the chat.\\n\\nEvents emitted:\\n\\n| Event | Fired when |\\n|---|---|\\n| `\\"open\\"` | Panel transitions closed \u2192 open (including via `sendMessage` or the shopper\'s own click). |\\n| `\\"close\\"` | Panel transitions open \u2192 closed. |\\n\\n### `generateStructuredContent(schema: object, prompt: string): Promise<unknown>`\\n\\nGenerates JSON matching `schema` from `prompt`, grounded in the store\'s live\\ncatalog. Resolves to the generated data object itself. Rejects with `Error`\\non any failure (invalid input, decline, validation failure, rate limit,\\nnetwork). See [structured-content.md](structured-content.md) for the full\\ncontract, schema support, caching, and error semantics.\\n\\nAccepts either a plain JSON Schema object or any object exposing a\\n`.toJSONSchema()` method (e.g. Zod schemas) \u2014 the conversion is called for\\nyou.\\n\\nThrows synchronously (rejects) if `prompt` is not a non-empty string or\\n`schema` is not an object.\\n\\n### `enableNotifications(): Promise<{ ok: boolean }>`\\n\\nOpts this browser into Web Push notifications from the store. Requirements:\\n\\n- Must be called from a user gesture (e.g. a click handler).\\n- The site must host GreatStore\'s `gs.js` service-worker file. By default the\\n SDK looks for it at `/gs.js`; if it\'s hosted elsewhere, point to it via an\\n attribute on the embed script tag:\\n `<script src=\\"\u2026/embed.js\\" data-push-sw-path=\\"/path/to/gs.js\\"></script>`.\\n\\nResolves `{ ok: true }` on success and `{ ok: false }` on any failure\\n(unsupported browser, no service worker hosted, permission denied). It never\\nrejects.\\n\\n## Page tools \u2014 WebMCP (`document.modelContext`)\\n\\nThe recommended way to expose page capabilities to the assistant is the\\nWebMCP standard. The GreatStore assistant discovers every tool registered on\\n`document.modelContext`, re-reading the list on each conversational turn \u2014\\nso tools registered mid-session appear on the next message without a reload.\\n\\n### Availability\\n\\nIf the browser implements WebMCP natively, `document.modelContext` is just\\nthere. Otherwise the SDK installs a spec-tracking polyfill on the page\\nautomatically \u2014 but asynchronously, so at your script\'s first run\\n`document.modelContext` may not exist yet. Two robust patterns:\\n\\n```js\\n// 1. Register once GreatStore is ready (polyfill is in place by then):\\nwindow.GreatStore?.ready.then(() => {\\n document.modelContext.registerTool(/* \u2026 */);\\n});\\n\\n// 2. Or ship your own polyfill (npm: @mcp-b/webmcp-polyfill) and register\\n// immediately \u2014 the SDK detects an existing implementation and uses it.\\n```\\n\\n(`navigator.modelContext` is a deprecated alias for the same object; use\\n`document.modelContext` in new code.)\\n\\n### `registerTool(tool, options?)`\\n\\n```ts\\ndocument.modelContext.registerTool(\\n {\\n name: string, // required, non-empty, unique on the page\\n description: string, // required \u2014 how the AI decides when to call it\\n inputSchema?: object, // JSON Schema for execute\'s args;\\n // defaults to { type: \\"object\\", properties: {} }\\n execute(args): Result | Promise<Result>,\\n },\\n options?: { signal?: AbortSignal }, // abort to unregister\\n);\\n```\\n\\n- **Result shape**: `execute` returns MCP content blocks \u2014\\n `{ content: [{ type: \\"text\\", text: \\"\u2026\\" }] }`. For structured data,\\n `JSON.stringify` it into `text`. Add `isError: true` to mark a handled\\n failure.\\n- **Errors**: a thrown error or rejected promise is delivered to the\\n assistant as a *failed* tool call carrying the error message \u2014 the\\n assistant can explain or adapt. Errors never escape into your page.\\n- **Duplicate names throw.** To replace a tool, abort its registration first.\\n- **Unregistration is `AbortSignal`-driven**: pass `{ signal }` and call\\n `abort()` when the tool\'s context goes away (SPA navigation, modal close).\\n A pre-aborted signal skips registration. (A legacy\\n `unregisterTool(name)` exists but is deprecated in the spec.)\\n- **Treat `args` as untrusted input**: values are AI-generated. Validate\\n before passing to your own APIs, and never `eval` anything from them.\\n\\nA complete tool, registered once GreatStore is ready:\\n\\n```js\\nwindow.GreatStore?.ready.then(() => {\\n document.modelContext.registerTool({\\n name: \\"add_to_cart\\",\\n description:\\n \\"Add a product variant to the shopper\'s cart on this site. \\" +\\n \\"Use when the shopper asks to add, buy, or get a product.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n variantId: { type: \\"string\\" },\\n quantity: { type: \\"integer\\", minimum: 1 },\\n },\\n required: [\\"variantId\\"],\\n },\\n async execute({ variantId, quantity }) {\\n const res = await fetch(\\"/cart/add.js\\", {\\n method: \\"POST\\",\\n headers: { \\"Content-Type\\": \\"application/json\\" },\\n body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),\\n });\\n if (!res.ok) throw new Error(`Cart add failed (${res.status})`);\\n const cart = await res.json();\\n return { content: [{ type: \\"text\\", text: JSON.stringify(cart) }] };\\n },\\n });\\n});\\n```\\n\\nReturning the fresh cart state after the mutation lets the assistant confirm\\naccurately. Good tool families: cart (`get_cart`, `add_to_cart`), navigation\\n(`go_to_page`), page state (`get_current_product`, `apply_filters`), UI\\n(`highlight_section`, `scroll_to_reviews`).\\n\\nAnd a context-scoped tool, unregistered via `AbortSignal`:\\n\\n```js\\nconst ac = new AbortController();\\ndocument.modelContext.registerTool(\\n {\\n name: \\"get_product_reviews\\",\\n description: \\"Read the reviews shown on the current product page.\\",\\n inputSchema: { type: \\"object\\", properties: {} },\\n execute: () => ({\\n content: [{ type: \\"text\\", text: JSON.stringify(collectReviews()) }],\\n }),\\n },\\n { signal: ac.signal },\\n);\\n\\n// On SPA route change away from the product page:\\nac.abort();\\n```\\n\\n## URL parameter: `?gs_chat=open`\\n\\nWhen the page URL carries `gs_chat=open`, the panel opens automatically once\\nthe embed mounts. The param is consumed and stripped from the URL via\\n`history.replaceState`, so a manual reload doesn\'t re-open the panel. Use it\\nin campaign links, emails, and post-login redirects.\\n\\n## Shopify login gate\\n\\nStores can enable a per-store \\"log in to chat\\" gate that applies **only to\\nthe embed** (never the store\'s GreatStore storefront). When enabled, the chat\\ninput is replaced with a login prompt until a Shopify customer signal is\\ndetected on the host page (standard Shopify globals/meta tags). The login\\nbutton sends the shopper through Shopify\'s customer login and back with\\n`?gs_chat=open` appended. This is a courtesy UX gate, not a security\\nboundary \u2014 don\'t rely on it to protect anything sensitive.\\n\\n## Troubleshooting\\n\\n| Symptom | Likely cause |\\n|---|---|\\n| Console: `[GreatStore] Chat is unavailable on <origin>\u2026 this domain isn\'t in the store\'s allowed domains` | The page\'s origin isn\'t in the store\'s allowed domains. Add it in store settings. Until then every SDK network call fails. |\\n| Console: `[GreatStore] Embed script must be loaded from <slug>.greatstore.ai/embed.js` | The script was copied/self-hosted instead of loaded from the store\'s embed URL. Always load it from `https://<slug>.greatstore.ai/embed.js`. |\\n| `generateStructuredContent` rejects with a rate-limit message | More than ~20 requests/minute from one visitor. Consolidate calls into fewer, richer schemas. |\\n| Panel won\'t auto-open on mobile after returning to the page | Intentional: the mobile drawer never auto-opens on resume \u2014 it would cover the content the shopper is reading. The transcript is preserved; they\'ll see it when they tap the launcher. |\\n| Tools registered but the assistant doesn\'t use them | Check the `description` \u2014 it\'s the only signal for *when* to call. Also confirm the registration ran (`document.modelContext` exists after `ready`) on the same page the conversation is on. |\\n","references/push-notifications.md":"# Web push re-engagement\\n\\nShoppers who opt in receive browser notifications from the store \u2014 under the\\nmerchant\'s own domain and branding, with the permission prompt shown inline\\non the merchant\'s page. Setup is two pieces: a single file hosted at the site\\nroot, and an opt-in button.\\n\\n## 1. Host `gs.js` at the site root\\n\\nDownload the store\'s loader and serve it at `/gs.js` on the merchant\'s\\ndomain:\\n\\n```\\nhttps://<slug>.greatstore.ai/gs.js \u2192 https://www.merchant-site.com/gs.js\\n```\\n\\nAlways download it from the **store\'s own subdomain** \u2014 the file is built for\\nthat store; don\'t copy one from elsewhere.\\n\\nThen load it with one tag (replacing the `embed.js` tag \u2014 `gs.js` injects the\\nembed for you and registers itself as the service worker):\\n\\n```html\\n<script src=\\"/gs.js\\"></script>\\n```\\n\\nHosting this file is what enables push. Without it, push is simply off \u2014\\n`enableNotifications()` returns `{ ok: false }` and nothing else changes.\\n\\n### Non-root hosting\\n\\nIf the platform can\'t serve files at the site root (e.g. Shopify themes\\nserve assets under a path), keep the regular `embed.js` tag and point it at\\nwhere the file lives \u2014 the path must be on the merchant\'s own origin:\\n\\n```html\\n<script\\n src=\\"https://my-store.greatstore.ai/embed.js\\"\\n data-push-sw-path=\\"/cdn/shop/files/gs.js\\"\\n></script>\\n```\\n\\n## 2. Offer the opt-in from a user gesture\\n\\n```js\\noptInButton.addEventListener(\\"click\\", async () => {\\n const { ok } = await window.GreatStore.enableNotifications();\\n optInButton.hidden = ok; // done \u2014 or quietly keep the button\\n});\\n```\\n\\nRules that make this work well:\\n\\n- **Always call it from a click** \u2014 browsers ignore or penalize permission\\n prompts that aren\'t user-initiated, and the call is designed for gesture\\n context.\\n- **Never prompt on page load.** Tie the button to a moment where\\n notifications have obvious value (\\"Notify me when this is back in stock\\",\\n post-purchase, after a chat conversation).\\n- `{ ok: false }` covers every failure the same way \u2014 unsupported browser, no\\n `gs.js` hosted, permission denied. It never rejects, and there\'s no popup\\n fallback. Design the button so a decline just leaves the page as it was;\\n don\'t show an error.\\n- The promise resolving `{ ok: true }` means this browser is subscribed.\\n There\'s nothing else to wire \u2014 notification delivery is handled by\\n GreatStore.\\n","references/structured-content.md":"# `generateStructuredContent` deep dive\\n\\nAI-generated, catalog-grounded JSON for UI you render yourself \u2014 the chat\\npanel is not involved.\\n\\n```js\\nconst data = await window.GreatStore.generateStructuredContent(schema, prompt);\\n```\\n\\n- `schema` \u2014 a JSON Schema describing the output (or a Zod schema exposing\\n `.toJSONSchema()`).\\n- `prompt` \u2014 what to generate.\\n- Resolves to **the generated JSON object itself**, matching the schema.\\n Rejects with an `Error` on any failure.\\n\\nCode samples here are framework-free reference implementations \u2014 re-express\\nthem in the host repo\'s framework (React, Vue, Shopify Liquid, \u2026) rather\\nthan retrofitting them as-is.\\n\\n## The rules that make it work well\\n\\nEach is unpacked in the sections below; this is the checklist.\\n\\n1. **Top level must be an object.** Want a list? Wrap it:\\n `{ type: \\"object\\", properties: { items: { type: \\"array\\", \u2026 } }, required: [\\"items\\"] }`.\\n2. **Steer with the prompt, not schema `description`s** \u2014 free-text schema\\n fields are stripped before the AI sees them. Use self-explanatory\\n property names (`benefitHeadline`, not `text1`).\\n3. **Only `require` what\'s guaranteed.** `required` is strictly enforced; if\\n the catalog can\'t ground a required field the whole call can fail. Require\\n structural fields, keep per-product details (image URLs, prices) optional,\\n and make rendering tolerate missing values.\\n4. **The AI can\'t see your page.** If page context matters, put it in the\\n prompt explicitly: `` `Generate care tips for the product \\"${productName}\\".` ``\\n5. **Responses are cached and shared** for up to ~24h across all visitors of\\n the same page + prompt + schema. Keep prompts deterministic per page \u2014\\n no timestamps, random values, or per-visitor data.\\n6. **Progressive enhancement, always.** Generate after the page renders into\\n a hidden container, reveal on success, leave the fallback on error. Render\\n generated strings via `textContent`, never `innerHTML`.\\n7. **One rich call beats many small ones** \u2014 there\'s a per-visitor rate limit\\n (~20 requests/minute); fetch multiple surfaces with one combined schema.\\n\\n## What actually happens\\n\\n1. The SDK posts your schema + prompt to the store\'s GreatStore endpoint,\\n along with the current **page URL and page title** (sent automatically \u2014\\n you don\'t pass them, and you can\'t override them).\\n2. GreatStore first **researches**: it looks up real data from the store\'s\\n live catalog (products, prices, availability, store info) using read-only\\n lookups. The page URL/title serve as hints about which product or category\\n to look up \u2014 they are *not* treated as a source of product data, and the\\n page\'s DOM is never read.\\n3. The AI then fills your schema from the researched data, under a strict\\n grounding contract: it must not invent product names, prices, images, IDs,\\n or descriptions. Fields it can\'t ground are omitted or `null`.\\n4. The output is validated against your schema (with internal retries) before\\n being returned and cached.\\n\\nThe request is anonymous by design \u2014 no shopper identity is attached \u2014 which\\nis what makes the response cacheable across all visitors.\\n\\n## Schema support\\n\\nTop level **must describe an object**: `type: \\"object\\"` (or a bare\\n`properties` / `anyOf`). To get a list, wrap it in an object property.\\n\\nSupported keywords (anything else is tolerated but ignored):\\n\\n- Types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`\\n- Structure: `properties`, `required`, `items`, `additionalProperties`\\n- Choice: `enum`, `const`, `anyOf`, `nullable`\\n- Constraints: `minimum`, `maximum`, `minLength`, `maxLength`, `minItems`,\\n `maxItems`, `pattern`, `format`, `default`\\n\\nValidation of the output is real: `required` is enforced, `enum`/`const`\\nmust match, numeric and length bounds are checked, and\\n`additionalProperties: false` rejects extra keys. Constraints are therefore a\\n*tool* \u2014 `maxItems: 4` reliably caps a list, `enum` reliably restricts a\\nfield \u2014 but every constraint is also a way for generation to fail, so apply\\nthem only where you\'d rather have no content than non-conforming content.\\n\\nZod schemas (or anything with a `.toJSONSchema()` method) are accepted and\\nconverted automatically.\\n\\n### Free-text schema fields are stripped\\n\\n`description`, `title`, and `example` are removed from the schema before the\\nAI sees it (they\'re a prompt-injection surface, so they\'re filtered\\nserver-side). Consequences:\\n\\n- Schema descriptions **cannot** steer generation. All steering lives in the\\n prompt string.\\n- Property *names* are the only in-schema signal of intent \u2014 make them\\n self-documenting: `ctaLabel`, `warmthRating`, `priceJustification`.\\n\\n## Prompting guide\\n\\nThe prompt is the entire instruction channel. A good prompt states, in order:\\n\\n1. **Context** \u2014 what page/situation the shopper is in, with concrete page\\n data inlined (the AI can\'t see your DOM):\\n `The shopper is viewing the product \\"Aurora Down Parka\\" on its product page.`\\n2. **Task** \u2014 what to generate, mapped loosely onto your schema\'s fields:\\n `Write a heading and 3 reasons to love it; each reason has a short title and one supporting sentence.`\\n3. **Grounding expectations** \u2014 what store data to draw on:\\n `Base every claim on the product\'s real materials, features, and price.`\\n4. **Voice** \u2014 tone and constraints:\\n `Warm and concrete. No exclamation marks, no generic marketing filler.`\\n\\nAnti-patterns:\\n\\n- **Per-visitor or per-moment data in the prompt** (names, cart contents,\\n timestamps, `Math.random()`): destroys the shared cache, so every visitor\\n pays full generation latency and the store pays for every call. If you\\n need per-shopper interaction, that\'s what `sendMessage` and the chat panel\\n are for.\\n- **Asking it to read the page** (\\"summarize the reviews shown below\\") \u2014 it\\n can\'t. Inline the data into the prompt instead, and keep what you inline\\n stable per page so caching still works.\\n- **Asking for data you should fetch yourself** (exact live stock numbers,\\n shipping ETAs). Use your own platform APIs for operational data; use\\n GreatStore for *editorial intelligence over the catalog*.\\n- **Burying instructions in schema descriptions** \u2014 stripped, see above.\\n\\n## Caching: design for it\\n\\nResponses are cached server-side for up to **24 hours**, keyed by the\\ncombination of page URL + page title + prompt + schema, and shared across all\\nvisitors. (Tracking query params like `utm_*`/`gclid` and the URL fragment\\nare ignored, so ad-tagged visits share the campaign-free page\'s cache entry.\\nMeaningful params like `?product=123` are part of the key.)\\n\\nPractical consequences:\\n\\n- **First visitor pays, the rest fly.** Expect a few seconds on a cache miss\\n and near-instant responses after. Design loading states for the miss case.\\n- **Same call on different pages = different content**, automatically \u2014 the\\n page URL is in the key and in the AI\'s hints. A single site-wide snippet\\n with a constant prompt yields per-page content for free.\\n- **Content refreshes roughly daily.** Don\'t build experiences that assume\\n minute-level freshness.\\n- **To force different content, change the prompt or schema** (e.g. a\\n campaign variant string that changes weekly \u2014 deliberate, low-cardinality\\n variation is fine; per-visitor cardinality is not).\\n\\nThe catalog research underneath is also cached briefly, so several distinct\\nsurfaces on the same page (different prompts/schemas) stay cheap even on\\ncold cache.\\n\\n## Errors and how to handle them\\n\\nThe promise rejects with `new Error(message)`. The message is\\ndeveloper-facing \u2014 never render it to shoppers. Cases:\\n\\n| Case | Message you\'ll see | Retry? |\\n|---|---|---|\\n| Bad input (empty prompt, non-object schema) | thrown immediately by the SDK | Fix the call |\\n| Invalid schema shape | `Invalid schema: \u2026` | Fix the schema |\\n| AI declined the request | `The assistant declined to generate content for this request.` | No \u2014 permanent for that prompt/schema. Rework the prompt. |\\n| Output couldn\'t satisfy the schema | `Failed to produce valid structured content` | No \u2014 usually `required`/constraints demand data the catalog lacks. Loosen the schema. |\\n| Rate limit (~20/min per visitor) | rate-limit message | Later \u2014 and consolidate calls |\\n| Network / server | varies | Next page load |\\n\\nThe uniform shopper-facing strategy: render into a hidden-by-default\\ncontainer, reveal on success, leave hidden (or show your static fallback) on\\nany rejection. One `try/catch`, no case analysis needed unless you\'re\\nlogging.\\n\\n## Performance pattern\\n\\nFire generation as early as possible without blocking render \u2014 top of your\\ndeferred script, before other work:\\n\\n```js\\nconst highlightsPromise = window.GreatStore?.generateStructuredContent\\n ? window.GreatStore.generateStructuredContent(schema, prompt).catch(() => null)\\n : Promise.resolve(null);\\n\\n// \u2026rest of page setup\u2026\\n\\nconst data = await highlightsPromise;\\nif (data) renderHighlights(data);\\n```\\n\\nThe `.catch(() => null)` attached immediately avoids unhandled-rejection\\nnoise while keeping a single render path.\\n\\nFor multiple surfaces on one page, prefer **one call with a combined\\nschema** over parallel calls \u2014 it\'s one research pass, one cache entry, and\\nno rate-limit pressure:\\n\\n```js\\nconst schema = {\\n type: \\"object\\",\\n properties: {\\n highlights: { /* \u2026 */ },\\n faq: { /* \u2026 */ },\\n crossSell: { /* \u2026 */ },\\n },\\n required: [\\"highlights\\"],\\n};\\n```\\n"}') : readTreeFromDisk(new URL("../../skill/", import.meta.url));
|
|
1496
1494
|
if (!cache2["SKILL.md"]) {
|
|
1497
1495
|
throw new Error("internal: agent skill tree is missing SKILL.md");
|
|
1498
1496
|
}
|
|
@@ -1647,8 +1645,8 @@ function parseVer(v) {
|
|
|
1647
1645
|
}
|
|
1648
1646
|
|
|
1649
1647
|
// src/index.ts
|
|
1650
|
-
var VERSION = true ? "0.0.
|
|
1651
|
-
var CHANGELOG = true ? "# Changelog\n\nAll notable changes to `@greatstore/cli` are recorded here. The format\nfollows [Keep a Changelog](https://keepachangelog.com/).\n\n## 0.0.25 \u2014 2026-06-11\n\n### Added\n- `gs skill` installs the GreatStore agent skill \u2014 a guide AI coding\n agents use to build with GreatStore: AI content for your own UI, chat\n entry points, page tools, custom in-chat components, push\n notifications, and the store's MCP endpoints. Installs into\n `./.claude/skills/`; use `--global` for `~/.claude/skills/`, or\n `--dir <path>` for agents that read skills from somewhere else. Run\n it again any time to update an installed copy.\n\n## 0.0.24 \u2014 2026-06-03\n\n### Changed\n- `gs init` in an existing project now fills in any scaffold files that\n are missing (for example, the `AGENTS.md` design guide added in\n 0.0.23) and leaves your own files alone. Pass `--force` to refresh\n every scaffold file to the latest version. Your pinned store\n (`.gsrc`) is never rewritten either way.\n\n## 0.0.23 \u2014 2026-06-03\n\n### Added\n- `gs init` now scaffolds an `AGENTS.md` (with `CLAUDE.md` and\n `GEMINI.md` symlinked to it) documenting the design rules every\n component should follow \u2014 use `em` rather than `rem` for sizing, and\n style from the provided brand CSS variables so components match the\n store's theme. It doubles as guidance for AI coding agents.\n\n## 0.0.22 \u2014 2026-06-02\n\n### Added\n- `gs list` now shows a link to each component's page in the dashboard,\n so you can jump straight to a component to preview or publish it. The\n link is also included in `gs list --json`.\n\n## 0.0.21 \u2014 2026-05-31\n\n### Changed\n- The `gs init` component scaffold now shows how to write **async**\n components that load data before they render \u2014 including validating\n inputs up front and signalling a failure by throwing. The scaffolded\n component no longer includes an `onError` prop; throw from an async\n component to report a failure instead.\n\n## 0.0.20 \u2014 2026-05-29\n\n### Added\n- `gs pull`, `gs push`, and `gs publish` now accept several component\n names at once (e.g. `gs publish header footer cart`). Each component\n is reported on its own line and one failure no longer stops the rest.\n\n## 0.0.19 \u2014 2026-05-28\n\n### Fixed\n- `gs login` on Windows no longer opens a sign-in URL with parameters\n stripped, which surfaced as a \"Missing redirect_uri or state\n parameter\" page in the browser.\n\n## 0.0.18 \u2014 2026-05-28\n\n### Changed\n- Push and publish errors now name the specific reason \u2014 including\n every failing field in `manifest.json` \u2014 instead of the previous\n generic message.\n\n## 0.0.17 \u2014 2026-05-28\n\n### Added\n- Each command now prints a one-line upgrade notice when a newer\n `@greatstore/cli` is available on npm.\n\n## 0.0.16 \u2014 2026-05-28\n\n### Changed\n- Simplified error messages.\n\n## 0.0.15 \u2014 2026-05-24\n\n### Added\n- Scaffolded `component.tsx` now declares the four injected lifecycle\n props (`onSendMessage`, `onCallTool`, `onClose`, `onError`) on\n `Props`. Use `onError(message)` to report expected failures (failed\n fetch, host action rejected, invalid host state) so the AI can\n recover on its next turn. Render-time crashes are reported for you.\n\n## 0.0.14 \u2014 2026-05-24\n\n### Changed\n- `gs build` output is now whitespace-minified \u2014 typically ~50% smaller.\n\n## 0.0.13 \u2014 2026-05-24\n\n### Changed\n- `gs build` prints the next-step hint (`gs push`, then `gs publish`).\n\n## 0.0.12 \u2014 2026-05-24\n\n### Fixed\n- `gs build` failing to load Vite in some setups.\n\n## 0.0.11 \u2014 2026-05-24\n\n### Added\n- Multi-component projects. `gs init` (no args) scaffolds the project\n root; `gs init <name>` adds a component under `components/<name>/`.\n- `gs build [<name>]` \u2014 compiles every `components/<name>/bundle.js`.\n- `gs push` (no args) uploads only the components that changed.\n- `gs pull` (no args, or `*`) downloads every component. Locally\n edited components are skipped; pass `--force` to overwrite.\n- Public `CHANGELOG.md`; `gs --version` prints recent entries.\n\n### Changed\n- A project folder ships to exactly one store. Only `gs init` accepts\n `--store`; every other command reads the slug from `.gsrc`. The old\n single-component layout is rejected with a migration hint.\n- `gs init` requires `--store <slug>` for a fresh root, and rejects\n `--store` on an existing root.\n- `gs init` no longer writes `build.mjs` \u2014 scripts call `gs build`.\n\n## 0.0.10 \u2014 2026-05-23\n\n### Changed\n- The sign-in browser tab auto-closes once `gs login` finishes.\n\n## 0.0.9 \u2014 2026-05-23\n\n### Changed\n- Scaffolded manifests include a `displayName` so the admin UI has a\n friendlier label.\n\n## 0.0.8 \u2014 2026-05-23\n\n### Changed\n- `gs push` and `gs publish` print a link to view the component.\n\n## 0.0.6 \u2014 2026-05-23\n\n### Changed\n- `gs --version` reads from the published package version.\n\n## 0.0.4 \u2014 2026-05-23\n\n### Changed\n- Scaffolded projects produce browser-ready bundles out of the box.\n\n## 0.0.3 \u2014 2026-05-23\n\n### Changed\n- Trimmed public README to the essentials.\n\n## 0.0.2 \u2014 2026-05-23\n\n### Fixed\n- Sign-in callback parameter handling.\n" : "";
|
|
1648
|
+
var VERSION = true ? "0.0.27" : "0.0.0-dev";
|
|
1649
|
+
var CHANGELOG = true ? "# Changelog\n\nAll notable changes to `@greatstore/cli` are recorded here. The format\nfollows [Keep a Changelog](https://keepachangelog.com/).\n\n## 0.0.27 \u2014 2026-06-11\n\n### Changed\n- Skill code samples now carry an explicit reference-only disclaimer:\n coding agents are told to re-express the logic in the host repo's\n framework (React, Vue, Shopify Liquid, Svelte, \u2026) instead of\n retrofitting the framework-free samples as-is.\n\n## 0.0.26 \u2014 2026-06-11\n\n### Added\n- The agent skill gains a \"GreatStore launchers\" recipe: a horizontally\n scrollable row of AI-generated chips, each an engaging first-person\n question about the current page that's sent to the assistant on tap.\n\n### Changed\n- Skill recipes are now one file each under `recipes/`, indexed from\n SKILL.md by a table with description and use-case columns.\n\n## 0.0.25 \u2014 2026-06-11\n\n### Added\n- `gs skill` installs the GreatStore agent skill \u2014 a guide AI coding\n agents use to build with GreatStore: AI content for your own UI, chat\n entry points, page tools, custom in-chat components, push\n notifications, and the store's MCP endpoints. Installs into\n `./.claude/skills/`; use `--global` for `~/.claude/skills/`, or\n `--dir <path>` for agents that read skills from somewhere else. Run\n it again any time to update an installed copy.\n\n## 0.0.24 \u2014 2026-06-03\n\n### Changed\n- `gs init` in an existing project now fills in any scaffold files that\n are missing (for example, the `AGENTS.md` design guide added in\n 0.0.23) and leaves your own files alone. Pass `--force` to refresh\n every scaffold file to the latest version. Your pinned store\n (`.gsrc`) is never rewritten either way.\n\n## 0.0.23 \u2014 2026-06-03\n\n### Added\n- `gs init` now scaffolds an `AGENTS.md` (with `CLAUDE.md` and\n `GEMINI.md` symlinked to it) documenting the design rules every\n component should follow \u2014 use `em` rather than `rem` for sizing, and\n style from the provided brand CSS variables so components match the\n store's theme. It doubles as guidance for AI coding agents.\n\n## 0.0.22 \u2014 2026-06-02\n\n### Added\n- `gs list` now shows a link to each component's page in the dashboard,\n so you can jump straight to a component to preview or publish it. The\n link is also included in `gs list --json`.\n\n## 0.0.21 \u2014 2026-05-31\n\n### Changed\n- The `gs init` component scaffold now shows how to write **async**\n components that load data before they render \u2014 including validating\n inputs up front and signalling a failure by throwing. The scaffolded\n component no longer includes an `onError` prop; throw from an async\n component to report a failure instead.\n\n## 0.0.20 \u2014 2026-05-29\n\n### Added\n- `gs pull`, `gs push`, and `gs publish` now accept several component\n names at once (e.g. `gs publish header footer cart`). Each component\n is reported on its own line and one failure no longer stops the rest.\n\n## 0.0.19 \u2014 2026-05-28\n\n### Fixed\n- `gs login` on Windows no longer opens a sign-in URL with parameters\n stripped, which surfaced as a \"Missing redirect_uri or state\n parameter\" page in the browser.\n\n## 0.0.18 \u2014 2026-05-28\n\n### Changed\n- Push and publish errors now name the specific reason \u2014 including\n every failing field in `manifest.json` \u2014 instead of the previous\n generic message.\n\n## 0.0.17 \u2014 2026-05-28\n\n### Added\n- Each command now prints a one-line upgrade notice when a newer\n `@greatstore/cli` is available on npm.\n\n## 0.0.16 \u2014 2026-05-28\n\n### Changed\n- Simplified error messages.\n\n## 0.0.15 \u2014 2026-05-24\n\n### Added\n- Scaffolded `component.tsx` now declares the four injected lifecycle\n props (`onSendMessage`, `onCallTool`, `onClose`, `onError`) on\n `Props`. Use `onError(message)` to report expected failures (failed\n fetch, host action rejected, invalid host state) so the AI can\n recover on its next turn. Render-time crashes are reported for you.\n\n## 0.0.14 \u2014 2026-05-24\n\n### Changed\n- `gs build` output is now whitespace-minified \u2014 typically ~50% smaller.\n\n## 0.0.13 \u2014 2026-05-24\n\n### Changed\n- `gs build` prints the next-step hint (`gs push`, then `gs publish`).\n\n## 0.0.12 \u2014 2026-05-24\n\n### Fixed\n- `gs build` failing to load Vite in some setups.\n\n## 0.0.11 \u2014 2026-05-24\n\n### Added\n- Multi-component projects. `gs init` (no args) scaffolds the project\n root; `gs init <name>` adds a component under `components/<name>/`.\n- `gs build [<name>]` \u2014 compiles every `components/<name>/bundle.js`.\n- `gs push` (no args) uploads only the components that changed.\n- `gs pull` (no args, or `*`) downloads every component. Locally\n edited components are skipped; pass `--force` to overwrite.\n- Public `CHANGELOG.md`; `gs --version` prints recent entries.\n\n### Changed\n- A project folder ships to exactly one store. Only `gs init` accepts\n `--store`; every other command reads the slug from `.gsrc`. The old\n single-component layout is rejected with a migration hint.\n- `gs init` requires `--store <slug>` for a fresh root, and rejects\n `--store` on an existing root.\n- `gs init` no longer writes `build.mjs` \u2014 scripts call `gs build`.\n\n## 0.0.10 \u2014 2026-05-23\n\n### Changed\n- The sign-in browser tab auto-closes once `gs login` finishes.\n\n## 0.0.9 \u2014 2026-05-23\n\n### Changed\n- Scaffolded manifests include a `displayName` so the admin UI has a\n friendlier label.\n\n## 0.0.8 \u2014 2026-05-23\n\n### Changed\n- `gs push` and `gs publish` print a link to view the component.\n\n## 0.0.6 \u2014 2026-05-23\n\n### Changed\n- `gs --version` reads from the published package version.\n\n## 0.0.4 \u2014 2026-05-23\n\n### Changed\n- Scaffolded projects produce browser-ready bundles out of the box.\n\n## 0.0.3 \u2014 2026-05-23\n\n### Changed\n- Trimmed public README to the essentials.\n\n## 0.0.2 \u2014 2026-05-23\n\n### Fixed\n- Sign-in callback parameter handling.\n" : "";
|
|
1652
1650
|
var HELP = `gs \u2014 GreatStore CLI (v${VERSION})
|
|
1653
1651
|
|
|
1654
1652
|
Usage:
|