@greatstore/cli 0.1.1 → 0.1.3
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 +15 -0
- package/README.md +15 -16
- package/dist/cli.js +178 -136
- 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 +121 -0
- package/dist/gs-skill/references/structured-content.md +252 -0
- package/package.json +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# `generateStructuredContent` deep dive
|
|
2
|
+
|
|
3
|
+
AI-generated, catalog-grounded JSON for UI you render yourself — the chat
|
|
4
|
+
panel is not involved.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
const data = await window.GreatStore.generateStructuredContent(schema, prompt);
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- `schema` — a JSON Schema describing the output (or a Zod schema exposing
|
|
11
|
+
`.toJSONSchema()`).
|
|
12
|
+
- `prompt` — what to generate.
|
|
13
|
+
- Resolves to **the generated JSON object itself**, matching the schema.
|
|
14
|
+
Rejects with an `Error` on any failure.
|
|
15
|
+
|
|
16
|
+
Code samples here are framework-free reference implementations — re-express
|
|
17
|
+
them in the host repo's framework (React, Vue, Shopify Liquid, …) rather
|
|
18
|
+
than retrofitting them as-is.
|
|
19
|
+
|
|
20
|
+
## The rules that make it work well
|
|
21
|
+
|
|
22
|
+
Each is unpacked in the sections below; this is the checklist.
|
|
23
|
+
|
|
24
|
+
1. **Top level must be an object.** Want a list? Wrap it:
|
|
25
|
+
`{ type: "object", properties: { items: { type: "array", … } }, required: ["items"] }`.
|
|
26
|
+
2. **Steer with the prompt, not schema `description`s** — free-text schema
|
|
27
|
+
fields are stripped before the AI sees them. Use self-explanatory
|
|
28
|
+
property names (`benefitHeadline`, not `text1`).
|
|
29
|
+
3. **Only `require` what's guaranteed.** `required` is strictly enforced; if
|
|
30
|
+
the catalog can't ground a required field the whole call can fail. Require
|
|
31
|
+
structural fields, keep per-product details (image URLs, prices) optional,
|
|
32
|
+
and make rendering tolerate missing values.
|
|
33
|
+
4. **Point, don't paste.** GreatStore researches the store's live catalog on
|
|
34
|
+
its own — name the entity (`` `…the product "${productName}" (SKU ${sku})` ``)
|
|
35
|
+
and let it look the facts up. Don't fetch specs/prices/descriptions
|
|
36
|
+
yourself and paste them into the prompt. The one thing it *can't* see is
|
|
37
|
+
your page, so page-only context (which page the shopper is on, what the
|
|
38
|
+
section is for) does belong in the prompt.
|
|
39
|
+
5. **Research is bounded by the store's connectors — validate before you
|
|
40
|
+
build.** The AI can only look up what the store's MCP connectors actually
|
|
41
|
+
provide. Ask for something outside them — currency conversion, live
|
|
42
|
+
shipping rates, review data the store never wired up — and it has nothing
|
|
43
|
+
to ground on, so it will decline, omit… or hallucinate. Before writing a
|
|
44
|
+
prompt or schema that depends on a data capability, run `gs connectors`
|
|
45
|
+
(and `gs connectors health`, [store-admin.md](store-admin.md)) and confirm
|
|
46
|
+
a connector for it exists; if it doesn't, don't ask for it.
|
|
47
|
+
6. **Responses are cached** for up to ~24h per page + prompt + schema —
|
|
48
|
+
shared across anonymous visitors, per-shopper for identified ones. Keep
|
|
49
|
+
prompts deterministic per page — no timestamps, random values, or
|
|
50
|
+
per-visitor data (GreatStore already knows who the shopper is; see below).
|
|
51
|
+
7. **Progressive enhancement, always.** Generate after the page renders into
|
|
52
|
+
a hidden container, reveal on success, leave the fallback on error. Render
|
|
53
|
+
generated strings via `textContent`, never `innerHTML`.
|
|
54
|
+
8. **One rich call beats many small ones** — there's a per-visitor rate limit
|
|
55
|
+
(~20 requests/minute); fetch multiple surfaces with one combined schema.
|
|
56
|
+
|
|
57
|
+
## What actually happens
|
|
58
|
+
|
|
59
|
+
1. The SDK posts your schema + prompt to the store's GreatStore endpoint,
|
|
60
|
+
along with the current **page URL and page title** (sent automatically —
|
|
61
|
+
you don't pass them, and you can't override them).
|
|
62
|
+
2. GreatStore first **researches**: it looks up real data from the store's
|
|
63
|
+
live catalog (products, prices, availability, store info) using read-only
|
|
64
|
+
lookups. The page URL/title serve as hints about which product or category
|
|
65
|
+
to look up — they are *not* treated as a source of product data, and the
|
|
66
|
+
page's DOM is never read. Research happens on GreatStore's side — your
|
|
67
|
+
prompt only needs to *point* it at the right SKU, product, or collection,
|
|
68
|
+
not carry the material. Its reach is exactly the store's MCP connectors:
|
|
69
|
+
validate with `gs connectors` ([store-admin.md](store-admin.md)) that a
|
|
70
|
+
connector for the data you want actually exists before you build on it —
|
|
71
|
+
research can't exceed the wired-up connectors, and prompts that assume
|
|
72
|
+
otherwise invite hallucinated filler.
|
|
73
|
+
3. The AI then fills your schema from the researched data, under a strict
|
|
74
|
+
grounding contract: it must not invent product names, prices, images, IDs,
|
|
75
|
+
or descriptions. Fields it can't ground are omitted or `null`. For an
|
|
76
|
+
identified shopper this step also sees their shopper profile — the same
|
|
77
|
+
identity the chat assistant has — so the output can be subtly personalized
|
|
78
|
+
without you passing anything about the visitor.
|
|
79
|
+
4. The output is validated against your schema (with internal retries) before
|
|
80
|
+
being returned and cached.
|
|
81
|
+
|
|
82
|
+
GreatStore already knows who's reading: identity rides the request the same
|
|
83
|
+
way it does for chat, and responses for identified shoppers are cached just
|
|
84
|
+
for them (anonymous visitors share one entry). The practical consequence:
|
|
85
|
+
never put shopper data in the prompt — it's redundant, and it poisons the
|
|
86
|
+
cache key.
|
|
87
|
+
|
|
88
|
+
## Schema support
|
|
89
|
+
|
|
90
|
+
Top level **must describe an object**: `type: "object"` (or a bare
|
|
91
|
+
`properties` / `anyOf`). To get a list, wrap it in an object property.
|
|
92
|
+
|
|
93
|
+
Supported keywords (anything else is tolerated but ignored):
|
|
94
|
+
|
|
95
|
+
- Types: `object`, `array`, `string`, `number`, `integer`, `boolean`, `null`
|
|
96
|
+
- Structure: `properties`, `required`, `items`, `additionalProperties`
|
|
97
|
+
- Choice: `enum`, `const`, `anyOf`, `nullable`
|
|
98
|
+
- Constraints: `minimum`, `maximum`, `minLength`, `maxLength`, `minItems`,
|
|
99
|
+
`maxItems`, `pattern`, `format`, `default`
|
|
100
|
+
|
|
101
|
+
Validation of the output is real: `required` is enforced, `enum`/`const`
|
|
102
|
+
must match, numeric and length bounds are checked, and
|
|
103
|
+
`additionalProperties: false` rejects extra keys. Constraints are therefore a
|
|
104
|
+
*tool* — `maxItems: 4` reliably caps a list, `enum` reliably restricts a
|
|
105
|
+
field — but every constraint is also a way for generation to fail, so apply
|
|
106
|
+
them only where you'd rather have no content than non-conforming content.
|
|
107
|
+
|
|
108
|
+
Zod schemas (or anything with a `.toJSONSchema()` method) are accepted and
|
|
109
|
+
converted automatically.
|
|
110
|
+
|
|
111
|
+
### Free-text schema fields are stripped
|
|
112
|
+
|
|
113
|
+
`description`, `title`, and `example` are removed from the schema before the
|
|
114
|
+
AI sees it (they're a prompt-injection surface, so they're filtered
|
|
115
|
+
server-side). Consequences:
|
|
116
|
+
|
|
117
|
+
- Schema descriptions **cannot** steer generation. All steering lives in the
|
|
118
|
+
prompt string.
|
|
119
|
+
- Property *names* are the only in-schema signal of intent — make them
|
|
120
|
+
self-documenting: `ctaLabel`, `warmthRating`, `priceJustification`.
|
|
121
|
+
|
|
122
|
+
## Prompting guide
|
|
123
|
+
|
|
124
|
+
The prompt is the entire instruction channel. A good prompt states, in order:
|
|
125
|
+
|
|
126
|
+
1. **Context** — what page/situation the shopper is in, naming the entity so
|
|
127
|
+
research targets the right thing (the AI can't see your DOM, so identify
|
|
128
|
+
it explicitly):
|
|
129
|
+
`The shopper is viewing the product "Aurora Down Parka" (SKU AUR-021) on its product page.`
|
|
130
|
+
2. **Task** — what to generate, mapped loosely onto your schema's fields:
|
|
131
|
+
`Write a heading and 3 reasons to love it; each reason has a short title and one supporting sentence.`
|
|
132
|
+
3. **Grounding expectations** — what store data to draw on:
|
|
133
|
+
`Base every claim on the product's real materials, features, and price.`
|
|
134
|
+
4. **Voice** — tone and constraints:
|
|
135
|
+
`Warm and concrete. No exclamation marks, no generic marketing filler.`
|
|
136
|
+
|
|
137
|
+
Note what's *not* in that prompt: no pasted specs, prices, or descriptions.
|
|
138
|
+
Pointing at the SKU is enough — GreatStore researches the rest itself, from
|
|
139
|
+
data that's live rather than whatever was true when you wrote the prompt.
|
|
140
|
+
|
|
141
|
+
Anti-patterns:
|
|
142
|
+
|
|
143
|
+
- **Pasting researched material into the prompt** (specs, prices,
|
|
144
|
+
descriptions you fetched from your platform's API). GreatStore researches
|
|
145
|
+
the live catalog itself — point it at the SKU/product/collection and let
|
|
146
|
+
it look things up. Pasted facts go stale, bloat the cache key, and compete
|
|
147
|
+
with the fresher data research returns.
|
|
148
|
+
- **Per-visitor or per-moment data in the prompt** (names, cart contents,
|
|
149
|
+
timestamps, `Math.random()`): destroys caching, so every visitor pays full
|
|
150
|
+
generation latency and the store pays for every call — and it's redundant,
|
|
151
|
+
because GreatStore already knows the shopper and personalizes for
|
|
152
|
+
identified ones server-side. If you need per-shopper *interaction*, that's
|
|
153
|
+
what `sendMessage` and the chat panel are for.
|
|
154
|
+
- **Asking for data no connector provides** ("convert the price to EUR",
|
|
155
|
+
"estimate delivery to the shopper's city"): research can't exceed the
|
|
156
|
+
store's MCP connectors, and the AI may hallucinate plausible-looking
|
|
157
|
+
values rather than leave the field empty. Validate first — `gs connectors`
|
|
158
|
+
shows what's wired up ([store-admin.md](store-admin.md)); if there's no
|
|
159
|
+
connector for it, don't ask for it.
|
|
160
|
+
- **Asking it to read the page** ("summarize the reviews shown below") — it
|
|
161
|
+
can't. Page-only data (review snippets, UGC, things that exist nowhere but
|
|
162
|
+
the DOM) is the one kind worth inlining — keep it stable per page so
|
|
163
|
+
caching still works.
|
|
164
|
+
- **Asking for minute-fresh operational data** (exact live stock counts,
|
|
165
|
+
delivery countdowns). Even when a connector could answer, responses cache
|
|
166
|
+
for up to ~24h — display operational data from your own platform APIs and
|
|
167
|
+
use GreatStore for *editorial intelligence over the catalog*.
|
|
168
|
+
- **Burying instructions in schema descriptions** — stripped, see above.
|
|
169
|
+
|
|
170
|
+
## Caching: design for it
|
|
171
|
+
|
|
172
|
+
Responses are cached server-side for up to **24 hours**, keyed by the
|
|
173
|
+
combination of page URL + page title + prompt + schema. Anonymous visitors
|
|
174
|
+
share one entry per key; identified shoppers each get their own (their
|
|
175
|
+
output may be personalized, so it's only ever served back to them).
|
|
176
|
+
(Tracking query params like `utm_*`/`gclid` and the URL fragment are
|
|
177
|
+
ignored, so ad-tagged visits share the campaign-free page's cache entry.
|
|
178
|
+
Meaningful params like `?product=123` are part of the key.)
|
|
179
|
+
|
|
180
|
+
Practical consequences:
|
|
181
|
+
|
|
182
|
+
- **The first render pays, repeats fly.** Expect a few seconds on a cache
|
|
183
|
+
miss and near-instant responses after — shared across all anonymous
|
|
184
|
+
traffic, per-shopper for identified traffic (the research underneath is
|
|
185
|
+
cached briefly and shared, so even those misses are cheaper than cold).
|
|
186
|
+
Design loading states for the miss case.
|
|
187
|
+
- **Same call on different pages = different content**, automatically — the
|
|
188
|
+
page URL is in the key and in the AI's hints. A single site-wide snippet
|
|
189
|
+
with a constant prompt yields per-page content for free.
|
|
190
|
+
- **Content refreshes roughly daily.** Don't build experiences that assume
|
|
191
|
+
minute-level freshness.
|
|
192
|
+
- **To force different content, change the prompt or schema** (e.g. a
|
|
193
|
+
campaign variant string that changes weekly — deliberate, low-cardinality
|
|
194
|
+
variation is fine; per-visitor cardinality is not).
|
|
195
|
+
|
|
196
|
+
The catalog research underneath is also cached briefly, so several distinct
|
|
197
|
+
surfaces on the same page (different prompts/schemas) stay cheap even on
|
|
198
|
+
cold cache.
|
|
199
|
+
|
|
200
|
+
## Errors and how to handle them
|
|
201
|
+
|
|
202
|
+
The promise rejects with `new Error(message)`. The message is
|
|
203
|
+
developer-facing — never render it to shoppers. Cases:
|
|
204
|
+
|
|
205
|
+
| Case | Message you'll see | Retry? |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| Bad input (empty prompt, non-object schema) | thrown immediately by the SDK | Fix the call |
|
|
208
|
+
| Invalid schema shape | `Invalid schema: …` | Fix the schema |
|
|
209
|
+
| AI declined the request | `The assistant declined to generate content for this request.` | No — permanent for that prompt/schema. Rework the prompt. |
|
|
210
|
+
| Output couldn't satisfy the schema | `Failed to produce valid structured content` | No — usually `required`/constraints demand data the catalog lacks. Loosen the schema. |
|
|
211
|
+
| Rate limit (~20/min per visitor) | rate-limit message | Later — and consolidate calls |
|
|
212
|
+
| Network / server | varies | Next page load |
|
|
213
|
+
|
|
214
|
+
The uniform shopper-facing strategy: render into a hidden-by-default
|
|
215
|
+
container, reveal on success, leave hidden (or show your static fallback) on
|
|
216
|
+
any rejection. One `try/catch`, no case analysis needed unless you're
|
|
217
|
+
logging.
|
|
218
|
+
|
|
219
|
+
## Performance pattern
|
|
220
|
+
|
|
221
|
+
Fire generation as early as possible without blocking render — top of your
|
|
222
|
+
deferred script, before other work:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
const highlightsPromise = window.GreatStore?.generateStructuredContent
|
|
226
|
+
? window.GreatStore.generateStructuredContent(schema, prompt).catch(() => null)
|
|
227
|
+
: Promise.resolve(null);
|
|
228
|
+
|
|
229
|
+
// …rest of page setup…
|
|
230
|
+
|
|
231
|
+
const data = await highlightsPromise;
|
|
232
|
+
if (data) renderHighlights(data);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The `.catch(() => null)` attached immediately avoids unhandled-rejection
|
|
236
|
+
noise while keeping a single render path.
|
|
237
|
+
|
|
238
|
+
For multiple surfaces on one page, prefer **one call with a combined
|
|
239
|
+
schema** over parallel calls — it's one research pass, one cache entry, and
|
|
240
|
+
no rate-limit pressure:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const schema = {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
highlights: { /* … */ },
|
|
247
|
+
faq: { /* … */ },
|
|
248
|
+
crossSell: { /* … */ },
|
|
249
|
+
},
|
|
250
|
+
required: ["highlights"],
|
|
251
|
+
};
|
|
252
|
+
```
|