@greatstore/cli 0.1.1 → 0.1.4
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 +32 -0
- package/README.md +15 -16
- package/dist/cli.js +422 -141
- package/dist/gs-skill/SKILL.md +83 -0
- package/dist/gs-skill/recipes/ask-about-this.md +31 -0
- package/dist/gs-skill/recipes/campaign-hero.md +43 -0
- package/dist/gs-skill/recipes/comparison-table.md +80 -0
- package/dist/gs-skill/recipes/complete-the-look.md +59 -0
- package/dist/gs-skill/recipes/custom-chat-button.md +25 -0
- package/dist/gs-skill/recipes/gift-finder-funnel.md +97 -0
- package/dist/gs-skill/recipes/launchers.md +109 -0
- package/dist/gs-skill/recipes/page-action-suite.md +90 -0
- package/dist/gs-skill/recipes/product-faq.md +58 -0
- package/dist/gs-skill/references/agents-and-mcp.md +35 -0
- package/dist/gs-skill/references/chat-components.md +224 -0
- package/dist/gs-skill/references/embed-api.md +381 -0
- package/dist/gs-skill/references/push-notifications.md +66 -0
- package/dist/gs-skill/references/store-admin.md +131 -0
- package/dist/gs-skill/references/structured-content.md +252 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: greatstore
|
|
3
|
+
description: 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, administering the store from the CLI (gs configure, gs connectors — origin allowlists, CSP hosts, MCP connectors), setting up web push re-engagement, or connecting AI agents to a store's MCP endpoints. Covers setup, schema design, caching behavior, component authoring, store administration, and ready-made recipes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Building with GreatStore
|
|
7
|
+
|
|
8
|
+
GreatStore gives a store an AI shopping assistant on two surfaces: a hosted
|
|
9
|
+
storefront at `https://<slug>.greatstore.ai/`, and an embedded chat widget on
|
|
10
|
+
the merchant's own site, installed with one script tag:
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<script src="https://my-store.greatstore.ai/embed.js"></script>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Everything else a site can do with GreatStore is documented in the
|
|
17
|
+
references below. Read the one that matches the task before writing code —
|
|
18
|
+
each facet has non-obvious rules (caching, grounding, result shapes, design
|
|
19
|
+
constraints) that the references spell out.
|
|
20
|
+
|
|
21
|
+
## Index
|
|
22
|
+
|
|
23
|
+
| Goal | Use | Read |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Install the widget; control the panel; readiness, events, troubleshooting | `window.GreatStore` SDK | [references/embed-api.md](references/embed-api.md) |
|
|
26
|
+
| 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) |
|
|
27
|
+
| Copy-paste on-page experiences | recipes built on the SDK | the [Recipes](#recipes) table below |
|
|
28
|
+
| Contextual **conversation entry points** anywhere on the page | `sendMessage(text)`, `open()`, `?gs_chat=open` | [references/embed-api.md](references/embed-api.md) |
|
|
29
|
+
| Feed the assistant **invisible page/component state** (current product, cart, selected variant) | `updateModelContext(text)`; component `onUpdateModelContext` | [references/embed-api.md](references/embed-api.md), [references/chat-components.md](references/chat-components.md) |
|
|
30
|
+
| Let the assistant **act on the page** (cart, navigation, filters) | WebMCP: `document.modelContext.registerTool(...)` | [references/embed-api.md](references/embed-api.md) |
|
|
31
|
+
| 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) |
|
|
32
|
+
| Expand a component's image in an **on-brand full-screen lightbox** | component `onShowLightbox({ src, originRect })` | [references/chat-components.md](references/chat-components.md) |
|
|
33
|
+
| **Re-engage shoppers** with browser notifications | merchant-hosted `gs.js` + `enableNotifications()` | [references/push-notifications.md](references/push-notifications.md) |
|
|
34
|
+
| 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) |
|
|
35
|
+
| **Administer the store** — read config/connectors to ground decisions; self-serve origin allowlists and CSP hosts | `gs configure`, `gs connectors` | [references/store-admin.md](references/store-admin.md) |
|
|
36
|
+
|
|
37
|
+
## Three things to know before any of it
|
|
38
|
+
|
|
39
|
+
- **Every code sample in this skill is a reference implementation, not a
|
|
40
|
+
drop-in.** Samples are framework-free vanilla JS so they stay portable —
|
|
41
|
+
re-express the same logic in the conventions of the repo you're working
|
|
42
|
+
in (React, Vue, Shopify Liquid sections, Svelte, …); never retrofit the
|
|
43
|
+
sample as-is into a codebase with its own framework.
|
|
44
|
+
- The page's domain **must be in the store's allowed domains**. If it isn't,
|
|
45
|
+
nothing works and the console shows
|
|
46
|
+
`[GreatStore] Chat is unavailable on <origin>…` — check this first whenever
|
|
47
|
+
the embed appears dead. You can verify and fix it yourself with the `gs`
|
|
48
|
+
CLI (read-merge-write on `extraOrigins` — see
|
|
49
|
+
[references/store-admin.md](references/store-admin.md)).
|
|
50
|
+
- Every SDK call is safe immediately after the script tag — pre-mount calls
|
|
51
|
+
queue and replay in order, and the SDK pre-warms itself in the background.
|
|
52
|
+
|
|
53
|
+
## Recipes
|
|
54
|
+
|
|
55
|
+
Complete, framework-free implementations, one per file. Shared conventions:
|
|
56
|
+
containers start `hidden` and reveal only on success (a failed generation
|
|
57
|
+
changes nothing); generated strings render via `textContent`, never
|
|
58
|
+
`innerHTML`; every recipe guards on `window.GreatStore`; prompts stay
|
|
59
|
+
deterministic per page so repeat renders hit the cache.
|
|
60
|
+
Inline real product/page data into prompts where the platform exposes it.
|
|
61
|
+
|
|
62
|
+
| Recipe | What it builds | Use it for |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| [GreatStore launchers](recipes/launchers.md) | Horizontally scrollable AI-generated chips — engaging first-person questions about the current page; tap to ask the assistant. | Instant engagement on any page type — product, collection, blog, home. Simple yet effective; start here. |
|
|
65
|
+
| [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 — anywhere a shopper hesitates. |
|
|
66
|
+
| [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. |
|
|
67
|
+
| [Comparison table](recipes/comparison-table.md) | AI-picked representative products compared on category-relevant criteria. | Collection pages where shoppers weigh options. |
|
|
68
|
+
| [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. |
|
|
69
|
+
| [Campaign hero](recipes/campaign-hero.md) | Seasonal homepage hero copy, cache-keyed to the ISO week. | Fresh homepage/campaign copy without manual rewrites. |
|
|
70
|
+
| [Gift finder funnel](recipes/gift-finder-funnel.md) | Quiz teaser → chat handoff → page-tool navigation; the full funnel. | Gifting seasons, guided discovery, homepage engagement. |
|
|
71
|
+
| [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. |
|
|
72
|
+
| [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. |
|
|
73
|
+
|
|
74
|
+
## How the facets combine
|
|
75
|
+
|
|
76
|
+
The strongest pattern is the **teaser → conversation → action** funnel:
|
|
77
|
+
`generateStructuredContent` renders a grounded teaser in the merchant's
|
|
78
|
+
design; each option's click handler calls `sendMessage` with the shopper's
|
|
79
|
+
choice, dropping them into a conversation with momentum; WebMCP page tools
|
|
80
|
+
and custom chat components let that conversation actually do things — add to
|
|
81
|
+
cart, configure a product, book a slot — so it ends in a conversion, not a
|
|
82
|
+
copy-paste. The [gift finder funnel](recipes/gift-finder-funnel.md)
|
|
83
|
+
recipe is this funnel end to end.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# "Ask about this" entry points (`sendMessage` only)
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
Zero-generation, instant, and often the biggest engagement win per line of
|
|
10
|
+
code. Sprinkle context-aware buttons wherever a shopper hesitates:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
const gs = window.GreatStore;
|
|
14
|
+
if (gs) {
|
|
15
|
+
sizeGuideLink.addEventListener("click", (e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
gs.sendMessage(`How does the sizing run on "${productName}"? I usually wear a medium.`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
shippingRow.querySelector(".ask").addEventListener("click", () => {
|
|
21
|
+
gs.sendMessage(`What are the shipping options and times for "${productName}"?`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
outOfStockBadge?.addEventListener("click", () => {
|
|
25
|
+
gs.sendMessage(`"${productName}" looks out of stock — is there anything similar in stock?`);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Write each message as something the shopper would plausibly say — it appears
|
|
31
|
+
in the transcript as their message.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Campaign hero with deliberate variation
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
Cache-friendly variation: key the prompt to a low-cardinality period, not to
|
|
10
|
+
time itself.
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
// ISO week number → one generation per store per week, shared by everyone.
|
|
14
|
+
const week = (d => {
|
|
15
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
16
|
+
t.setUTCDate(t.getUTCDate() + 4 - (t.getUTCDay() || 7));
|
|
17
|
+
return `${t.getUTCFullYear()}-W${Math.ceil((((t - Date.UTC(t.getUTCFullYear(), 0, 1)) / 864e5) + 1) / 7)}`;
|
|
18
|
+
})(new Date());
|
|
19
|
+
|
|
20
|
+
const data = await window.GreatStore.generateStructuredContent(
|
|
21
|
+
{
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
headline: { type: "string", maxLength: 60 },
|
|
25
|
+
subline: { type: "string", maxLength: 120 },
|
|
26
|
+
featuredProductName: { type: "string", nullable: true },
|
|
27
|
+
ctaChatMessage: { type: "string", maxLength: 120 },
|
|
28
|
+
},
|
|
29
|
+
required: ["headline", "subline", "ctaChatMessage"],
|
|
30
|
+
},
|
|
31
|
+
`Variant ${week}. Write a homepage hero for this store: a headline and ` +
|
|
32
|
+
`subline spotlighting a real product or category that fits the current ` +
|
|
33
|
+
`season, plus ctaChatMessage — the first-person message a shopper ` +
|
|
34
|
+
`would send to start shopping for it.`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
heroHeadline.textContent = data.headline;
|
|
38
|
+
heroSubline.textContent = data.subline;
|
|
39
|
+
heroCta.addEventListener("click", () =>
|
|
40
|
+
window.GreatStore.sendMessage(data.ctaChatMessage)
|
|
41
|
+
);
|
|
42
|
+
hero.hidden = false;
|
|
43
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Collection-page comparison table
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<section id="gs-compare" hidden>
|
|
11
|
+
<h3>Quick comparison</h3>
|
|
12
|
+
<table><thead id="gsc-head"></thead><tbody id="gsc-body"></tbody></table>
|
|
13
|
+
</section>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
(async () => {
|
|
17
|
+
if (!window.GreatStore?.generateStructuredContent) return;
|
|
18
|
+
const collection = "winter jackets"; // ← your collection name
|
|
19
|
+
try {
|
|
20
|
+
const data = await window.GreatStore.generateStructuredContent(
|
|
21
|
+
{
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
criteria: {
|
|
25
|
+
type: "array", minItems: 2, maxItems: 4,
|
|
26
|
+
items: { type: "string", maxLength: 25 },
|
|
27
|
+
},
|
|
28
|
+
rows: {
|
|
29
|
+
type: "array", minItems: 2, maxItems: 4,
|
|
30
|
+
items: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
productName: { type: "string" },
|
|
34
|
+
bestFor: { type: "string", maxLength: 60 },
|
|
35
|
+
values: {
|
|
36
|
+
type: "array",
|
|
37
|
+
items: { type: "string", maxLength: 60 },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ["productName", "bestFor", "values"],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["criteria", "rows"],
|
|
45
|
+
},
|
|
46
|
+
`The shopper is browsing the "${collection}" collection. Pick the ` +
|
|
47
|
+
`3-4 most representative products and compare them. Choose the ` +
|
|
48
|
+
`criteria a shopper actually decides on for this category. ` +
|
|
49
|
+
`"values" must align with "criteria" by index. Add a one-line ` +
|
|
50
|
+
`"bestFor" verdict per product. Only use real products and facts.`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const head = document.getElementById("gsc-head");
|
|
54
|
+
const hr = document.createElement("tr");
|
|
55
|
+
for (const h of ["Product", ...data.criteria, "Best for"]) {
|
|
56
|
+
const th = document.createElement("th");
|
|
57
|
+
th.textContent = h;
|
|
58
|
+
hr.append(th);
|
|
59
|
+
}
|
|
60
|
+
head.append(hr);
|
|
61
|
+
|
|
62
|
+
const body = document.getElementById("gsc-body");
|
|
63
|
+
for (const row of data.rows) {
|
|
64
|
+
const tr = document.createElement("tr");
|
|
65
|
+
const cells = [row.productName, ...(row.values ?? []), row.bestFor];
|
|
66
|
+
for (let i = 0; i < data.criteria.length + 2; i++) {
|
|
67
|
+
const td = document.createElement("td");
|
|
68
|
+
td.textContent = cells[i] ?? "—";
|
|
69
|
+
tr.append(td);
|
|
70
|
+
}
|
|
71
|
+
body.append(tr);
|
|
72
|
+
}
|
|
73
|
+
document.getElementById("gs-compare").hidden = false;
|
|
74
|
+
} catch {}
|
|
75
|
+
})();
|
|
76
|
+
</script>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Note the index-aligned `values`/`criteria` trick and the `?? "—"` guard —
|
|
80
|
+
grounding means a value the catalog can't support may be missing.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# "Complete the look" cross-sell strip
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const data = await window.GreatStore.generateStructuredContent(
|
|
11
|
+
{
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
intro: { type: "string", maxLength: 90 },
|
|
15
|
+
picks: {
|
|
16
|
+
type: "array", minItems: 2, maxItems: 4,
|
|
17
|
+
items: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
productName: { type: "string" },
|
|
21
|
+
reason: { type: "string", maxLength: 90 },
|
|
22
|
+
imageUrl: { type: "string", nullable: true },
|
|
23
|
+
productUrl: { type: "string", nullable: true },
|
|
24
|
+
},
|
|
25
|
+
required: ["productName", "reason"],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["picks"],
|
|
30
|
+
},
|
|
31
|
+
`The shopper is viewing "${productName}". From the store's real catalog, ` +
|
|
32
|
+
`pick 2-4 products that genuinely pair with it and say why each one ` +
|
|
33
|
+
`completes the look or use-case. Include image and product URLs only ` +
|
|
34
|
+
`if known.`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
for (const pick of data.picks) {
|
|
38
|
+
const card = document.createElement("a");
|
|
39
|
+
if (pick.productUrl) card.href = pick.productUrl;
|
|
40
|
+
if (pick.imageUrl) {
|
|
41
|
+
const img = document.createElement("img");
|
|
42
|
+
img.src = pick.imageUrl;
|
|
43
|
+
img.alt = pick.productName;
|
|
44
|
+
img.loading = "lazy";
|
|
45
|
+
card.append(img);
|
|
46
|
+
}
|
|
47
|
+
const name = document.createElement("strong");
|
|
48
|
+
name.textContent = pick.productName;
|
|
49
|
+
const why = document.createElement("p");
|
|
50
|
+
why.textContent = pick.reason;
|
|
51
|
+
card.append(name, why);
|
|
52
|
+
strip.append(card);
|
|
53
|
+
}
|
|
54
|
+
strip.hidden = false;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`imageUrl`/`productUrl` are `nullable` and optional in the render — the
|
|
58
|
+
grounding contract means they're only present when the catalog actually has
|
|
59
|
+
them. Never `require` URLs.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Custom chat button synced to panel state
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
Replace the default launcher with your own UI using the lifecycle surface:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const gs = window.GreatStore;
|
|
13
|
+
const btn = document.getElementById("my-chat-button");
|
|
14
|
+
|
|
15
|
+
gs.ready.then(() => { btn.hidden = false; });
|
|
16
|
+
btn.addEventListener("click", () => gs.toggle());
|
|
17
|
+
|
|
18
|
+
gs.on("open", () => btn.setAttribute("aria-expanded", "true"));
|
|
19
|
+
gs.on("close", () => btn.setAttribute("aria-expanded", "false"));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`ready` resolves even when `.then()` is attached after mount, so script
|
|
23
|
+
ordering doesn't matter. The `open`/`close` events also fire for opens the
|
|
24
|
+
SDK triggers itself (`sendMessage`, `?gs_chat=open`), keeping your button
|
|
25
|
+
state honest.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Gift finder funnel (teaser → conversation → action)
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
The flagship pattern: an AI-generated quiz card in your design, whose answers
|
|
10
|
+
drop the shopper into a chat that can act on the page. Uses the
|
|
11
|
+
`onGreatStoreReady` helper from the
|
|
12
|
+
[embed API reference](../references/embed-api.md#availability) — define
|
|
13
|
+
that once and it works for every use on the page.
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<section id="gift-finder" hidden>
|
|
17
|
+
<h3 id="gf-question"></h3>
|
|
18
|
+
<div id="gf-options"></div>
|
|
19
|
+
</section>
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
// Depending on where this script tag sits relative to the embed script
|
|
23
|
+
// tag, this inline script could run either before embed.js has executed
|
|
24
|
+
// at all, or after it's already mounted — checking `window.GreatStore`
|
|
25
|
+
// synchronously and bailing if it's not there yet would silently skip
|
|
26
|
+
// the whole funnel in the first case, and a bare
|
|
27
|
+
// `addEventListener("greatstore:ready", ...)` would silently miss the
|
|
28
|
+
// (one-shot, already-fired) event in the second.
|
|
29
|
+
// onGreatStoreReady (see the embed API reference) handles both.
|
|
30
|
+
onGreatStoreReady(async (gs) => {
|
|
31
|
+
// Tools the resulting conversation can use — discovered on its next
|
|
32
|
+
// turn automatically.
|
|
33
|
+
document.modelContext.registerTool({
|
|
34
|
+
name: "go_to_product",
|
|
35
|
+
description:
|
|
36
|
+
"Navigate the shopper to a product page on this site. Use when " +
|
|
37
|
+
"the shopper picks a product they want to see.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: { url: { type: "string" } },
|
|
41
|
+
required: ["url"],
|
|
42
|
+
},
|
|
43
|
+
execute({ url }) {
|
|
44
|
+
const u = new URL(String(url), location.origin);
|
|
45
|
+
if (u.origin !== location.origin) throw new Error("Only same-site URLs allowed");
|
|
46
|
+
location.assign(u.href);
|
|
47
|
+
return { content: [{ type: "text", text: "Navigating." }] };
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const data = await gs.generateStructuredContent(
|
|
53
|
+
{
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
question: { type: "string", maxLength: 80 },
|
|
57
|
+
options: {
|
|
58
|
+
type: "array",
|
|
59
|
+
minItems: 3,
|
|
60
|
+
maxItems: 4,
|
|
61
|
+
items: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
label: { type: "string", maxLength: 30 },
|
|
65
|
+
chatMessage: { type: "string", maxLength: 140 },
|
|
66
|
+
},
|
|
67
|
+
required: ["label", "chatMessage"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["question", "options"],
|
|
72
|
+
},
|
|
73
|
+
"Create one engaging gift-finder opening question for this store, " +
|
|
74
|
+
"with 3-4 answer options that map to real areas of the catalog. " +
|
|
75
|
+
"For each option also write chatMessage: the message a shopper " +
|
|
76
|
+
"would send to a shopping assistant after picking it, phrased in " +
|
|
77
|
+
"first person (e.g. \"I'm shopping for my dad who loves hiking\")."
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
document.getElementById("gf-question").textContent = data.question;
|
|
81
|
+
const wrap = document.getElementById("gf-options");
|
|
82
|
+
for (const opt of data.options) {
|
|
83
|
+
const btn = document.createElement("button");
|
|
84
|
+
btn.type = "button";
|
|
85
|
+
btn.textContent = opt.label;
|
|
86
|
+
btn.addEventListener("click", () => gs.sendMessage(opt.chatMessage));
|
|
87
|
+
wrap.append(btn);
|
|
88
|
+
}
|
|
89
|
+
document.getElementById("gift-finder").hidden = false;
|
|
90
|
+
} catch {}
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Why it works: the teaser costs one cached generation per page, each
|
|
96
|
+
click opens a conversation that already has direction, and `go_to_product`
|
|
97
|
+
lets the conversation end on a product page instead of in a dead end.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# GreatStore launchers — AI question chips
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
A horizontally scrollable row of chips, each a highly engaging first-person
|
|
10
|
+
question about the current page. Tapping a chip sends that question to the
|
|
11
|
+
assistant — `generateStructuredContent` writes the questions, `sendMessage`
|
|
12
|
+
fires them. Simple yet effective: it works on every page type, costs one
|
|
13
|
+
cached generation per page, and every tap starts a conversation that already
|
|
14
|
+
has a great opening line.
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<div id="gs-launchers" hidden></div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
#gs-launchers {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: 0.5em;
|
|
23
|
+
overflow-x: auto;
|
|
24
|
+
-webkit-overflow-scrolling: touch;
|
|
25
|
+
scrollbar-width: none;
|
|
26
|
+
padding: 0.5em 1em;
|
|
27
|
+
}
|
|
28
|
+
#gs-launchers::-webkit-scrollbar { display: none; }
|
|
29
|
+
#gs-launchers button {
|
|
30
|
+
flex: 0 0 auto;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
border: 1px solid #ddd;
|
|
33
|
+
border-radius: 999px;
|
|
34
|
+
padding: 0.5em 0.9em;
|
|
35
|
+
background: #fff;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
39
|
+
|
|
40
|
+
<script>
|
|
41
|
+
(async () => {
|
|
42
|
+
const gs = window.GreatStore;
|
|
43
|
+
if (!gs?.generateStructuredContent) return;
|
|
44
|
+
try {
|
|
45
|
+
const data = await gs.generateStructuredContent(
|
|
46
|
+
{
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
launchers: {
|
|
50
|
+
type: "array", minItems: 4, maxItems: 6,
|
|
51
|
+
items: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
chip: { type: "string", maxLength: 32 },
|
|
55
|
+
question: { type: "string", maxLength: 140 },
|
|
56
|
+
},
|
|
57
|
+
required: ["chip", "question"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ["launchers"],
|
|
62
|
+
},
|
|
63
|
+
`The shopper is on the page "${document.title}". Write 4-6 launcher ` +
|
|
64
|
+
`chips for a shopping assistant. For each, "question" is a highly ` +
|
|
65
|
+
`engaging first-person question this shopper would genuinely want ` +
|
|
66
|
+
`answered on this page — specific to its product, category, or ` +
|
|
67
|
+
`content, never generic — and "chip" is a 2-4 word teaser of it. ` +
|
|
68
|
+
`Vary the angles: fit and use, comparisons, gifting, care, what's ` +
|
|
69
|
+
`popular.`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const row = document.getElementById("gs-launchers");
|
|
73
|
+
for (const { chip, question } of data.launchers) {
|
|
74
|
+
const btn = document.createElement("button");
|
|
75
|
+
btn.type = "button";
|
|
76
|
+
btn.textContent = chip;
|
|
77
|
+
btn.title = question;
|
|
78
|
+
btn.addEventListener("click", () => gs.sendMessage(question));
|
|
79
|
+
row.append(btn);
|
|
80
|
+
}
|
|
81
|
+
row.hidden = false;
|
|
82
|
+
} catch {}
|
|
83
|
+
})();
|
|
84
|
+
</script>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Why it works:
|
|
88
|
+
|
|
89
|
+
- **The chip is the teaser, the question is the payload.** A 2-4 word chip
|
|
90
|
+
scans instantly; the full first-person question lands in the transcript
|
|
91
|
+
reading like something the shopper typed, and gives the assistant a
|
|
92
|
+
well-formed prompt. The `title` attribute previews the full question on
|
|
93
|
+
hover.
|
|
94
|
+
- **Per-page for free.** The page URL is part of the generation context and
|
|
95
|
+
the cache key, so one site-wide snippet yields different chips on every
|
|
96
|
+
page — each served from cache on repeat renders.
|
|
97
|
+
- **Placement is the lever.** Under the product title, above the grid on
|
|
98
|
+
collections, at the end of a blog post — wherever a shopper pauses to
|
|
99
|
+
wonder, the chips name the question for them.
|
|
100
|
+
|
|
101
|
+
Tips:
|
|
102
|
+
|
|
103
|
+
- Inline the product or collection name into the prompt when the platform
|
|
104
|
+
exposes it — it beats relying on `document.title`.
|
|
105
|
+
- Restyle the chips to the site's design system; the CSS above is just the
|
|
106
|
+
scroll mechanics (flex row, `overflow-x: auto`, hidden scrollbars,
|
|
107
|
+
`white-space: nowrap`).
|
|
108
|
+
- Resist adding more than ~6 chips — a launcher row is an invitation, not a
|
|
109
|
+
sitemap.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Page-action suite (WebMCP)
|
|
2
|
+
|
|
3
|
+
> **Treat this code as a reference, not a drop-in.** Samples are
|
|
4
|
+
> framework-free vanilla JS so they stay portable — re-express the same
|
|
5
|
+
> logic in the conventions of the repo you're working in (React, Vue,
|
|
6
|
+
> Shopify Liquid sections, Svelte, …) instead of retrofitting the sample
|
|
7
|
+
> as-is.
|
|
8
|
+
|
|
9
|
+
Give every conversation on the site real capabilities. Register once in a
|
|
10
|
+
shared snippet, after the SDK is ready (which guarantees
|
|
11
|
+
`document.modelContext` exists) — using the `onGreatStoreReady` helper from
|
|
12
|
+
the [embed API reference](../references/embed-api.md#availability), which
|
|
13
|
+
handles both load orderings (embed script not run yet vs. already mounted)
|
|
14
|
+
that a plain `window.GreatStore?.ready.then()` or a bare
|
|
15
|
+
`addEventListener("greatstore:ready", ...)` each only cover one side of:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
onGreatStoreReady(() => {
|
|
19
|
+
const text = (value) => ({
|
|
20
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
document.modelContext.registerTool({
|
|
24
|
+
name: "get_cart",
|
|
25
|
+
description:
|
|
26
|
+
"Read the shopper's current cart on this site: items, quantities, " +
|
|
27
|
+
"and totals. Use before answering any cart question.",
|
|
28
|
+
inputSchema: { type: "object", properties: {} },
|
|
29
|
+
async execute() {
|
|
30
|
+
return text(await (await fetch("/cart.js")).json());
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
document.modelContext.registerTool({
|
|
35
|
+
name: "add_to_cart",
|
|
36
|
+
description:
|
|
37
|
+
"Add a product variant to the cart on this site. Use when the " +
|
|
38
|
+
"shopper asks to add or buy something. Confirm the variant with " +
|
|
39
|
+
"the shopper first if ambiguous.",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
variantId: { type: "string" },
|
|
44
|
+
quantity: { type: "integer", minimum: 1, maximum: 10 },
|
|
45
|
+
},
|
|
46
|
+
required: ["variantId"],
|
|
47
|
+
},
|
|
48
|
+
async execute({ variantId, quantity }) {
|
|
49
|
+
if (!variantId) throw new Error("variantId is required");
|
|
50
|
+
const res = await fetch("/cart/add.js", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) throw new Error(`Could not add to cart (${res.status})`);
|
|
56
|
+
document.dispatchEvent(new CustomEvent("cart:refresh"));
|
|
57
|
+
return text(await res.json());
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
document.modelContext.registerTool({
|
|
62
|
+
name: "get_current_page",
|
|
63
|
+
description:
|
|
64
|
+
"Read what page the shopper is currently on, including structured " +
|
|
65
|
+
"product data when on a product page.",
|
|
66
|
+
inputSchema: { type: "object", properties: {} },
|
|
67
|
+
execute() {
|
|
68
|
+
return text({
|
|
69
|
+
url: location.href,
|
|
70
|
+
title: document.title,
|
|
71
|
+
productJson: document.querySelector("#product-json")?.textContent ?? null,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Principles at work: throw on failure (the assistant explains and recovers),
|
|
79
|
+
return fresh state after mutations (the assistant confirms accurately), cap
|
|
80
|
+
quantities in the schema, and notify your own UI (`cart:refresh`) so the
|
|
81
|
+
page reflects what the AI did.
|
|
82
|
+
|
|
83
|
+
For a product-page-only tool, register with an `AbortSignal` and abort on
|
|
84
|
+
SPA navigation:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const ac = new AbortController();
|
|
88
|
+
document.modelContext.registerTool(reviewsTool, { signal: ac.signal });
|
|
89
|
+
router.onLeave("/products/:handle", () => ac.abort());
|
|
90
|
+
```
|