@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,381 @@
|
|
|
1
|
+
# `window.GreatStore` API reference
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<script src="https://my-store.greatstore.ai/embed.js"></script>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
One script tag, anywhere on the page (end of `<body>` preferred), with the
|
|
10
|
+
store's slug in the host. The `window.GreatStore` object exists synchronously
|
|
11
|
+
once the script executes; every method below is safe to call before the chat
|
|
12
|
+
UI has finished loading — pre-mount calls are queued and replayed in order
|
|
13
|
+
once it mounts. The SDK pre-warms its chat bundle in the background
|
|
14
|
+
automatically; the panel stays closed until `open()` / `toggle()` /
|
|
15
|
+
`sendMessage()` is called or the shopper clicks the launcher.
|
|
16
|
+
|
|
17
|
+
Requirements:
|
|
18
|
+
|
|
19
|
+
- The page's domain must be in the store's **allowed domains** (GreatStore
|
|
20
|
+
store settings) — see Troubleshooting below for the failure signature.
|
|
21
|
+
- If the store also wants push notifications, host `gs.js` at the site root
|
|
22
|
+
and load that instead of `embed.js` — it injects the embed for you. See
|
|
23
|
+
[push-notifications.md](push-notifications.md).
|
|
24
|
+
|
|
25
|
+
Code samples throughout are framework-free reference implementations —
|
|
26
|
+
re-express them in the host repo's framework (React, Vue, Shopify Liquid,
|
|
27
|
+
…) rather than retrofitting them as-is. **Using React?** `@greatstore/react`
|
|
28
|
+
wraps all of this in a `<GreatStore>` component with typed props (no
|
|
29
|
+
`data-*` attribute quoting) and clean mount/unmount for SPA routing —
|
|
30
|
+
`npm install @greatstore/react`.
|
|
31
|
+
|
|
32
|
+
## Appearance overrides
|
|
33
|
+
|
|
34
|
+
The store's configured appearance (theme colors, fonts, roundedness, panel
|
|
35
|
+
position, mobile bar, AI disclaimer) is the default everywhere. To make the
|
|
36
|
+
embed look or behave differently on a specific page — e.g. matching a
|
|
37
|
+
campaign landing page's palette — set `data-*` attributes on the embed
|
|
38
|
+
script tag. Every attribute is optional; anything you don't set falls back
|
|
39
|
+
to the store's configured value.
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<script
|
|
43
|
+
src="https://my-store.greatstore.ai/embed.js"
|
|
44
|
+
data-theme-mode="dark"
|
|
45
|
+
data-theme-radius="rounded"
|
|
46
|
+
data-theme-panel-position="right"
|
|
47
|
+
data-theme-mobile-bar="false"
|
|
48
|
+
data-z-index="9999"
|
|
49
|
+
data-theme-brand-color="#1a1a2e"
|
|
50
|
+
data-theme-surface-color="#ffffff"
|
|
51
|
+
data-theme-text-color="#111111"
|
|
52
|
+
data-theme-font="Inter, sans-serif"
|
|
53
|
+
data-ai-disclaimer="false"
|
|
54
|
+
></script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Attribute | Values | Overrides |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `data-theme-mode` | `auto` \| `light` \| `dark` \| `custom` | Color scheme. |
|
|
60
|
+
| `data-theme-radius` | `sharp` \| `default` \| `rounded` | Corner roundedness. |
|
|
61
|
+
| `data-theme-panel-position` | `left` \| `right` \| `middle` | Desktop panel placement. |
|
|
62
|
+
| `data-theme-mobile-bar` | `true` \| `false` | Whether the collapsed mobile bar shows. |
|
|
63
|
+
| `data-z-index` | integer, `0`–`2147483647` | Stacking order of the chat overlay on your page. Lower it if something on the page must stay above the chat. |
|
|
64
|
+
| `data-theme-font` | CSS font-family string | Primary font. |
|
|
65
|
+
| `data-theme-font-secondary` | CSS font-family string | Secondary font. |
|
|
66
|
+
| `data-theme-brand-color` | any CSS color | Brand/primary color (only used in `custom` mode). |
|
|
67
|
+
| `data-theme-surface-color` | any CSS color | Surface/background color (only used in `custom` mode). |
|
|
68
|
+
| `data-theme-text-color` | any CSS color | Text color (only used in `custom` mode). |
|
|
69
|
+
| `data-ai-disclaimer` | `true` \| `false` | Shows or hides the AI disclaimer line. |
|
|
70
|
+
| `data-ai-disclaimer-text` | string, up to 200 chars | Custom disclaimer message (implies shown, unless `data-ai-disclaimer="false"` is also set). |
|
|
71
|
+
|
|
72
|
+
Not overridable this way: display name, assistant name, logo, and icons —
|
|
73
|
+
those stay whatever's configured in the store's admin.
|
|
74
|
+
|
|
75
|
+
Invalid values (unrecognized enum, malformed color, disallowed font
|
|
76
|
+
characters) are silently ignored and fall back to the configured default.
|
|
77
|
+
|
|
78
|
+
## Properties
|
|
79
|
+
|
|
80
|
+
| Property | Type | Description |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `slug` | `string` | The store identifier the script was loaded for. |
|
|
83
|
+
| `host` | `string` | `"greatstore.ai"`. |
|
|
84
|
+
| `embedHost` | `string` | Origin the embed assets load from, e.g. `https://<slug>.greatstore.ai`. |
|
|
85
|
+
| `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 readiness signal to gate on. |
|
|
86
|
+
|
|
87
|
+
A `greatstore:ready` `CustomEvent` (with the SDK object as `detail`) is also
|
|
88
|
+
dispatched on `window` at the moment `ready` resolves, for declarative
|
|
89
|
+
tooling. Unlike the promise, the listener must be attached before mount
|
|
90
|
+
completes — attach it before (or immediately after) the embed script tag.
|
|
91
|
+
|
|
92
|
+
## Methods
|
|
93
|
+
|
|
94
|
+
### `load(): void`
|
|
95
|
+
|
|
96
|
+
Pre-warms the chat bundle and identity in the background without opening the
|
|
97
|
+
panel. Called automatically when `embed.js` runs, so you rarely need it.
|
|
98
|
+
Idempotent.
|
|
99
|
+
|
|
100
|
+
### `open(): void` / `close(): void` / `toggle(): void`
|
|
101
|
+
|
|
102
|
+
Open, close, or toggle the chat panel. On desktop the panel is a floating
|
|
103
|
+
side panel; under 768px viewport width it's a full-height drawer. All three
|
|
104
|
+
queue if called before mount.
|
|
105
|
+
|
|
106
|
+
### `sendMessage(text: string): void`
|
|
107
|
+
|
|
108
|
+
Sends `text` as the shopper's own visible chat message and **opens the panel
|
|
109
|
+
if it's closed**. The text is trimmed; empty or whitespace-only strings are
|
|
110
|
+
silently dropped. Queues if called before mount.
|
|
111
|
+
|
|
112
|
+
This is the highest-leverage one-liner in the SDK: any element on the page
|
|
113
|
+
can become a conversation entry point with context baked into the question.
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
document.querySelector("#ask-fit").addEventListener("click", () => {
|
|
117
|
+
window.GreatStore.sendMessage(
|
|
118
|
+
`I'm looking at "${productName}" — how does the sizing run?`
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Because the message renders as if the shopper typed it, write it in the
|
|
124
|
+
shopper's voice. It is not a hidden-context channel — don't stuff it with
|
|
125
|
+
invisible instructions or data dumps. For that, use `updateModelContext`.
|
|
126
|
+
|
|
127
|
+
### `updateModelContext(context: string): void`
|
|
128
|
+
|
|
129
|
+
Injects free-text **background context** about what the shopper is doing on
|
|
130
|
+
the page — the product they're viewing, what's in their cart, their account
|
|
131
|
+
tier — so the assistant can factor it in. Unlike `sendMessage`, this is
|
|
132
|
+
**invisible**: it never renders as a chat message and doesn't open the panel.
|
|
133
|
+
|
|
134
|
+
Each call **replaces** the value from the previous call — it never appends.
|
|
135
|
+
Keep one current snapshot; re-call it whenever the page state changes. Pass
|
|
136
|
+
an empty string to clear it. The text is read on the next chat turn, so set
|
|
137
|
+
it before (or while) the shopper is chatting. Queues if called before mount.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
// Keep the assistant aware of the current product as the shopper browses.
|
|
141
|
+
function syncContext() {
|
|
142
|
+
window.GreatStore.updateModelContext(
|
|
143
|
+
`Viewing "${product.title}" (${product.price}). In stock: ${product.inStock}. ` +
|
|
144
|
+
`Cart: ${cart.count} item(s), subtotal ${cart.subtotal}.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
syncContext();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Write it as concise notes for the model, not prose for the shopper. The
|
|
151
|
+
context is page-controlled, so the assistant treats it as background
|
|
152
|
+
information, not as instructions — don't rely on it to change the assistant's
|
|
153
|
+
rules or persona.
|
|
154
|
+
|
|
155
|
+
### `on(event: string, handler: (...args) => void): () => void`
|
|
156
|
+
|
|
157
|
+
Subscribe to SDK events. Returns an unsubscribe function. Listeners attached
|
|
158
|
+
before mount are queued and wired up at mount. Handler exceptions are caught
|
|
159
|
+
and reported — they won't break the chat.
|
|
160
|
+
|
|
161
|
+
Events emitted:
|
|
162
|
+
|
|
163
|
+
| Event | Fired when |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `"open"` | Panel transitions closed → open (including via `sendMessage` or the shopper's own click). |
|
|
166
|
+
| `"close"` | Panel transitions open → closed. |
|
|
167
|
+
|
|
168
|
+
### `generateStructuredContent(schema: object, prompt: string): Promise<unknown>`
|
|
169
|
+
|
|
170
|
+
Generates JSON matching `schema` from `prompt`, grounded in the store's live
|
|
171
|
+
catalog. Resolves to the generated data object itself. Rejects with `Error`
|
|
172
|
+
on any failure (invalid input, decline, validation failure, rate limit,
|
|
173
|
+
network). See [structured-content.md](structured-content.md) for the full
|
|
174
|
+
contract, schema support, caching, and error semantics.
|
|
175
|
+
|
|
176
|
+
Accepts either a plain JSON Schema object or any object exposing a
|
|
177
|
+
`.toJSONSchema()` method (e.g. Zod schemas) — the conversion is called for
|
|
178
|
+
you.
|
|
179
|
+
|
|
180
|
+
Throws synchronously (rejects) if `prompt` is not a non-empty string or
|
|
181
|
+
`schema` is not an object.
|
|
182
|
+
|
|
183
|
+
### `enableNotifications(): Promise<{ ok: boolean }>`
|
|
184
|
+
|
|
185
|
+
Opts this browser into Web Push notifications from the store. Requirements:
|
|
186
|
+
|
|
187
|
+
- Must be called from a user gesture (e.g. a click handler).
|
|
188
|
+
- The site must host GreatStore's `gs.js` service-worker file. By default the
|
|
189
|
+
SDK looks for it at `/gs.js`; if it's hosted elsewhere, point to it via an
|
|
190
|
+
attribute on the embed script tag:
|
|
191
|
+
`<script src="…/embed.js" data-push-sw-path="/path/to/gs.js"></script>`.
|
|
192
|
+
|
|
193
|
+
Resolves `{ ok: true }` on success and `{ ok: false }` on any failure
|
|
194
|
+
(unsupported browser, no service worker hosted, permission denied). It never
|
|
195
|
+
rejects.
|
|
196
|
+
|
|
197
|
+
### `destroy(): void`
|
|
198
|
+
|
|
199
|
+
Fully tears down a mounted panel: unmounts the chat UI, closes any open voice
|
|
200
|
+
connection, and removes the embed's DOM/listeners from the page. Use this
|
|
201
|
+
when your page is done with the embed for good — e.g. a single-page app
|
|
202
|
+
navigating away from the only route that should show it.
|
|
203
|
+
|
|
204
|
+
No-ops (with a console warning) if nothing is mounted. The cached identity
|
|
205
|
+
and downloaded chat bundle are kept, so a later `load()` (or any call that
|
|
206
|
+
triggers a mount, like `open()`) mounts a fresh panel without a network
|
|
207
|
+
round-trip for either. `ready` becomes a new pending promise at the moment
|
|
208
|
+
`destroy()` is called, resolving again once the next mount completes:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
window.GreatStore.destroy();
|
|
212
|
+
// ...later, on the page/route where the embed should come back:
|
|
213
|
+
window.GreatStore.load();
|
|
214
|
+
await window.GreatStore.ready; // resolves once the fresh mount is done
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Page tools — WebMCP (`document.modelContext`)
|
|
218
|
+
|
|
219
|
+
The recommended way to expose page capabilities to the assistant is the
|
|
220
|
+
WebMCP standard. The GreatStore assistant discovers every tool registered on
|
|
221
|
+
`document.modelContext`, re-reading the list on each conversational turn —
|
|
222
|
+
so tools registered mid-session appear on the next message without a reload.
|
|
223
|
+
|
|
224
|
+
### Availability
|
|
225
|
+
|
|
226
|
+
If the browser implements WebMCP natively, `document.modelContext` is just
|
|
227
|
+
there. Otherwise the SDK installs a minimal fallback the instant `embed.js`
|
|
228
|
+
starts running — synchronously, no async gap — so `document.modelContext`
|
|
229
|
+
is normally available immediately. The embed script tag deliberately has no
|
|
230
|
+
`async`/`defer` — a script with neither blocks parsing and runs at its own
|
|
231
|
+
position, which matters if the page also has another script (a nav widget,
|
|
232
|
+
an analytics tag) registering its own WebMCP tools: whichever executes
|
|
233
|
+
first wins that registration, and only a synchronous, unconditionally-first
|
|
234
|
+
script gives GreatStore's fallback shim a real chance of installing before
|
|
235
|
+
one of those calls happens. That said, your own code can still end up on
|
|
236
|
+
either side of two different races relative to `embed.js`, depending on
|
|
237
|
+
where your script tag sits and whether it uses `async`/`defer` itself:
|
|
238
|
+
|
|
239
|
+
- **Your script runs before `embed.js` has executed at all** — `window.GreatStore`
|
|
240
|
+
doesn't exist yet. `window.GreatStore?.ready` silently evaluates to
|
|
241
|
+
`undefined` here (optional chaining swallows it), so accessing `.then()`
|
|
242
|
+
on it throws or (written more defensively) just does nothing.
|
|
243
|
+
- **Your script runs after `embed.js` has already mounted** — the
|
|
244
|
+
`greatstore:ready` event already fired once, in the past.
|
|
245
|
+
`window.addEventListener("greatstore:ready", ...)` attached now will
|
|
246
|
+
never see it: the event isn't replayed for late listeners, unlike a
|
|
247
|
+
resolved Promise (`.then()` on an already-resolved Promise still fires).
|
|
248
|
+
|
|
249
|
+
Neither `.ready.then(...)` alone nor `addEventListener("greatstore:ready", ...)`
|
|
250
|
+
alone is safe against both orderings. Use both, picking whichever is valid
|
|
251
|
+
at the moment your code runs:
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
function onGreatStoreReady(callback) {
|
|
255
|
+
if (window.GreatStore?.ready) {
|
|
256
|
+
// embed.js has already run — .then() on its ready Promise fires
|
|
257
|
+
// immediately if it already resolved, or once it does.
|
|
258
|
+
window.GreatStore.ready.then(() => callback(window.GreatStore));
|
|
259
|
+
} else {
|
|
260
|
+
// embed.js hasn't run yet — wait for the one-shot event it'll dispatch
|
|
261
|
+
// once it has. Safe to attach now: nothing can fire it between this
|
|
262
|
+
// check and the listener attaching, since JS execution isn't preemptible.
|
|
263
|
+
window.addEventListener(
|
|
264
|
+
"greatstore:ready",
|
|
265
|
+
(event) => callback(event.detail),
|
|
266
|
+
{ once: true },
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
onGreatStoreReady(() => {
|
|
272
|
+
document.modelContext.registerTool(/* … */);
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
(`navigator.modelContext` is a deprecated alias for the same object; use
|
|
277
|
+
`document.modelContext` in new code.)
|
|
278
|
+
|
|
279
|
+
### `registerTool(tool, options?)`
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
document.modelContext.registerTool(
|
|
283
|
+
{
|
|
284
|
+
name: string, // required, non-empty, unique on the page
|
|
285
|
+
description: string, // required — how the AI decides when to call it
|
|
286
|
+
inputSchema?: object, // JSON Schema for execute's args;
|
|
287
|
+
// defaults to { type: "object", properties: {} }
|
|
288
|
+
execute(args): Result | Promise<Result>,
|
|
289
|
+
},
|
|
290
|
+
options?: { signal?: AbortSignal }, // abort to unregister
|
|
291
|
+
);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
- **Result shape**: `execute` returns MCP content blocks —
|
|
295
|
+
`{ content: [{ type: "text", text: "…" }] }`. For structured data,
|
|
296
|
+
`JSON.stringify` it into `text`. Add `isError: true` to mark a handled
|
|
297
|
+
failure.
|
|
298
|
+
- **Errors**: a thrown error or rejected promise is delivered to the
|
|
299
|
+
assistant as a *failed* tool call carrying the error message — the
|
|
300
|
+
assistant can explain or adapt. Errors never escape into your page.
|
|
301
|
+
- **Duplicate names throw.** To replace a tool, abort its registration first.
|
|
302
|
+
- **Unregistration is `AbortSignal`-driven**: pass `{ signal }` and call
|
|
303
|
+
`abort()` when the tool's context goes away (SPA navigation, modal close).
|
|
304
|
+
A pre-aborted signal skips registration. (A legacy
|
|
305
|
+
`unregisterTool(name)` exists but is deprecated in the spec.)
|
|
306
|
+
- **Treat `args` as untrusted input**: values are AI-generated. Validate
|
|
307
|
+
before passing to your own APIs, and never `eval` anything from them.
|
|
308
|
+
|
|
309
|
+
A complete tool, registered once GreatStore is ready (using the
|
|
310
|
+
`onGreatStoreReady` helper defined above):
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
onGreatStoreReady(() => {
|
|
314
|
+
document.modelContext.registerTool({
|
|
315
|
+
name: "add_to_cart",
|
|
316
|
+
description:
|
|
317
|
+
"Add a product variant to the shopper's cart on this site. " +
|
|
318
|
+
"Use when the shopper asks to add, buy, or get a product.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
variantId: { type: "string" },
|
|
323
|
+
quantity: { type: "integer", minimum: 1 },
|
|
324
|
+
},
|
|
325
|
+
required: ["variantId"],
|
|
326
|
+
},
|
|
327
|
+
async execute({ variantId, quantity }) {
|
|
328
|
+
const res = await fetch("/cart/add.js", {
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers: { "Content-Type": "application/json" },
|
|
331
|
+
body: JSON.stringify({ id: variantId, quantity: quantity ?? 1 }),
|
|
332
|
+
});
|
|
333
|
+
if (!res.ok) throw new Error(`Cart add failed (${res.status})`);
|
|
334
|
+
const cart = await res.json();
|
|
335
|
+
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Returning the fresh cart state after the mutation lets the assistant confirm
|
|
342
|
+
accurately. Good tool families: cart (`get_cart`, `add_to_cart`), navigation
|
|
343
|
+
(`go_to_page`), page state (`get_current_product`, `apply_filters`), UI
|
|
344
|
+
(`highlight_section`, `scroll_to_reviews`).
|
|
345
|
+
|
|
346
|
+
And a context-scoped tool, unregistered via `AbortSignal`:
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
const ac = new AbortController();
|
|
350
|
+
document.modelContext.registerTool(
|
|
351
|
+
{
|
|
352
|
+
name: "get_product_reviews",
|
|
353
|
+
description: "Read the reviews shown on the current product page.",
|
|
354
|
+
inputSchema: { type: "object", properties: {} },
|
|
355
|
+
execute: () => ({
|
|
356
|
+
content: [{ type: "text", text: JSON.stringify(collectReviews()) }],
|
|
357
|
+
}),
|
|
358
|
+
},
|
|
359
|
+
{ signal: ac.signal },
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// On SPA route change away from the product page:
|
|
363
|
+
ac.abort();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## URL parameter: `?gs_chat=open`
|
|
367
|
+
|
|
368
|
+
When the page URL carries `gs_chat=open`, the panel opens automatically once
|
|
369
|
+
the embed mounts. The param is consumed and stripped from the URL via
|
|
370
|
+
`history.replaceState`, so a manual reload doesn't re-open the panel. Use it
|
|
371
|
+
in campaign links, emails, and post-login redirects.
|
|
372
|
+
|
|
373
|
+
## Troubleshooting
|
|
374
|
+
|
|
375
|
+
| Symptom | Likely cause |
|
|
376
|
+
|---|---|
|
|
377
|
+
| Console: `[GreatStore] Chat is unavailable on <origin>… 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. |
|
|
378
|
+
| 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`. |
|
|
379
|
+
| `generateStructuredContent` rejects with a rate-limit message | More than ~20 requests/minute from one visitor. Consolidate calls into fewer, richer schemas. |
|
|
380
|
+
| Panel won't auto-open on mobile after returning to the page | Intentional: the mobile drawer never auto-opens on resume — it would cover the content the shopper is reading. The transcript is preserved; they'll see it when they tap the launcher. |
|
|
381
|
+
| Tools registered but the assistant doesn't use them | Check the `description` — 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. |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Web push re-engagement
|
|
2
|
+
|
|
3
|
+
Shoppers who opt in receive browser notifications from the store — under the
|
|
4
|
+
merchant's own domain and branding, with the permission prompt shown inline
|
|
5
|
+
on the merchant's page. Setup is two pieces: a single file hosted at the site
|
|
6
|
+
root, and an opt-in button.
|
|
7
|
+
|
|
8
|
+
## 1. Host `gs.js` at the site root
|
|
9
|
+
|
|
10
|
+
Download the store's loader and serve it at `/gs.js` on the merchant's
|
|
11
|
+
domain:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
https://<slug>.greatstore.ai/gs.js → https://www.merchant-site.com/gs.js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Always download it from the **store's own subdomain** — the file is built for
|
|
18
|
+
that store; don't copy one from elsewhere.
|
|
19
|
+
|
|
20
|
+
Then load it with one tag (replacing the `embed.js` tag — `gs.js` injects the
|
|
21
|
+
embed for you and registers itself as the service worker):
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script src="/gs.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Hosting this file is what enables push. Without it, push is simply off —
|
|
28
|
+
`enableNotifications()` returns `{ ok: false }` and nothing else changes.
|
|
29
|
+
|
|
30
|
+
### Non-root hosting
|
|
31
|
+
|
|
32
|
+
If the platform can't serve files at the site root (e.g. Shopify themes
|
|
33
|
+
serve assets under a path), keep the regular `embed.js` tag and point it at
|
|
34
|
+
where the file lives — the path must be on the merchant's own origin:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script
|
|
38
|
+
src="https://my-store.greatstore.ai/embed.js"
|
|
39
|
+
data-push-sw-path="/cdn/shop/files/gs.js"
|
|
40
|
+
></script>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 2. Offer the opt-in from a user gesture
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
optInButton.addEventListener("click", async () => {
|
|
47
|
+
const { ok } = await window.GreatStore.enableNotifications();
|
|
48
|
+
optInButton.hidden = ok; // done — or quietly keep the button
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Rules that make this work well:
|
|
53
|
+
|
|
54
|
+
- **Always call it from a click** — browsers ignore or penalize permission
|
|
55
|
+
prompts that aren't user-initiated, and the call is designed for gesture
|
|
56
|
+
context.
|
|
57
|
+
- **Never prompt on page load.** Tie the button to a moment where
|
|
58
|
+
notifications have obvious value ("Notify me when this is back in stock",
|
|
59
|
+
post-purchase, after a chat conversation).
|
|
60
|
+
- `{ ok: false }` covers every failure the same way — unsupported browser, no
|
|
61
|
+
`gs.js` hosted, permission denied. It never rejects, and there's no popup
|
|
62
|
+
fallback. Design the button so a decline just leaves the page as it was;
|
|
63
|
+
don't show an error.
|
|
64
|
+
- The promise resolving `{ ok: true }` means this browser is subscribed.
|
|
65
|
+
There's nothing else to wire — notification delivery is handled by
|
|
66
|
+
GreatStore.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Store administration from the CLI
|
|
2
|
+
|
|
3
|
+
The `gs` CLI is a full admin surface for a GreatStore store, mirroring the
|
|
4
|
+
merchant's admin dashboard one-to-one: `gs configure` ↔ the Configure panel,
|
|
5
|
+
`gs connectors` ↔ the Connectors panel, `gs apps` ↔ the Apps panel. Same
|
|
6
|
+
fields, same behaviour — anything you change is what the merchant sees in
|
|
7
|
+
their dashboard.
|
|
8
|
+
|
|
9
|
+
That makes the CLI the way a coding agent grounds and unblocks its own work:
|
|
10
|
+
read the store's configuration to make better decisions, and make the narrow
|
|
11
|
+
class of additive, integration-enabling changes yourself instead of telling
|
|
12
|
+
the user to go click through a dashboard.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm install -g @greatstore/cli # or npx @greatstore/cli <command>
|
|
18
|
+
gs login # one-time browser sign-in (needs a human)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Credentials persist across runs. Admin commands take `--store <slug>`
|
|
22
|
+
directly, or read the nearest `.gsrc` (`{"store":"my-store"}`) — so they work
|
|
23
|
+
from any repo, not just a scaffolded component project. Every read supports
|
|
24
|
+
`--json` for machine-readable output.
|
|
25
|
+
|
|
26
|
+
## Read freely — always safe
|
|
27
|
+
|
|
28
|
+
Reading store state is never destructive. Do it whenever it would improve a
|
|
29
|
+
decision:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
gs configure --store my-store --json # brand config
|
|
33
|
+
gs connectors --store my-store --json # MCP connectors feeding the assistant
|
|
34
|
+
gs connectors health --store my-store # live-probe them (reports tool counts)
|
|
35
|
+
gs apps list --store my-store --json # deployed custom chat components
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
What each read is good for:
|
|
39
|
+
|
|
40
|
+
- **`gs configure`** — the store's `extraOrigins` (is the site you're
|
|
41
|
+
integrating actually allowlisted? — see the embed troubleshooting note in
|
|
42
|
+
[embed-api.md](embed-api.md)), `theme` (match generated on-page UI to the
|
|
43
|
+
brand's colors/radius), `storeLink`, and CSP host lists.
|
|
44
|
+
- **`gs connectors`** — which MCP connectors the assistant has, and whether
|
|
45
|
+
the Maker MCP is on. This tells you what the assistant can actually *do*:
|
|
46
|
+
check it before building features that lean on catalog data (e.g.
|
|
47
|
+
[structured content](structured-content.md) grounding), and before
|
|
48
|
+
registering WebMCP page tools that would duplicate a capability a
|
|
49
|
+
server-side connector already provides.
|
|
50
|
+
- **`gs connectors health`** — a connector being listed doesn't mean it's
|
|
51
|
+
reachable; this probes each one live. Run it when assistant behaviour
|
|
52
|
+
suggests a tool isn't responding.
|
|
53
|
+
|
|
54
|
+
## Safe to change without asking
|
|
55
|
+
|
|
56
|
+
When the task the user gave you requires it, these changes are yours to
|
|
57
|
+
make: they're additive, easy to undo, and invisible to shoppers.
|
|
58
|
+
|
|
59
|
+
- **`extraOrigins`** — the origin allowlist for the embed. Installing the
|
|
60
|
+
widget on a new site/staging domain? Add that origin yourself instead of
|
|
61
|
+
reporting "ask the merchant to allowlist it".
|
|
62
|
+
- **`cspScriptHosts` / `cspConnectHosts`** — hosts the storefront accepts
|
|
63
|
+
for scripts/connections. Add a host when a custom chat component
|
|
64
|
+
legitimately needs a third-party SDK or API.
|
|
65
|
+
|
|
66
|
+
**List flags replace the stored list — always read-merge-write.** Fetch the
|
|
67
|
+
current value, append yours, write the union:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
gs configure --store my-store --json
|
|
71
|
+
# extraOrigins is ["https://shop.example.com"] → write both, comma-separated:
|
|
72
|
+
gs configure set --store my-store \
|
|
73
|
+
--extraOrigins "https://shop.example.com,https://staging.example.com"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Never drop an entry you didn't add, and say what you changed (and why) when
|
|
77
|
+
you report back to the user.
|
|
78
|
+
|
|
79
|
+
## Ask the merchant first
|
|
80
|
+
|
|
81
|
+
Everything below is shopper-visible or changes what the live assistant can
|
|
82
|
+
do for every shopper. Propose it, don't do it unprompted:
|
|
83
|
+
|
|
84
|
+
- **Brand-visible config** — `displayName`, `assistantName`, `storeLink`,
|
|
85
|
+
`theme`, and asset uploads
|
|
86
|
+
(`gs configure upload|clear icon|logoLight|logoDark`).
|
|
87
|
+
- **Connector mutations** — `gs connectors add|remove|enable|disable` and
|
|
88
|
+
`gs connectors maker on|off`. (`add` probes the connector for tool
|
|
89
|
+
discovery before saving and refuses if it doesn't answer; `--force`
|
|
90
|
+
overrides. Still: adding capabilities to the merchant's assistant is the
|
|
91
|
+
merchant's call.)
|
|
92
|
+
- **Anything that clears** — passing `""` to empty a field, removing list
|
|
93
|
+
entries, `clear`ing assets.
|
|
94
|
+
|
|
95
|
+
When the user has *explicitly asked* for one of these ("set the assistant's
|
|
96
|
+
name to Voyager", "connect this MCP server"), that's the go-ahead — do it
|
|
97
|
+
and confirm the result with a read.
|
|
98
|
+
|
|
99
|
+
## Command reference
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
gs configure [--json] show configuration
|
|
103
|
+
gs configure set --<field> <value> displayName, assistantName,
|
|
104
|
+
storeLink, extraOrigins a,b,
|
|
105
|
+
cspScriptHosts a,b, cspConnectHosts a,b
|
|
106
|
+
("" clears a field)
|
|
107
|
+
gs configure set --theme.<field> <value> one theme field, merged into the rest:
|
|
108
|
+
mode auto|light|dark|custom,
|
|
109
|
+
radius sharp|default|rounded,
|
|
110
|
+
panelPosition left|right|middle,
|
|
111
|
+
fontFamily, secondaryFontFamily,
|
|
112
|
+
mobileBottomBar true|false, zIndex,
|
|
113
|
+
brandColor/surfaceColor/textColor and
|
|
114
|
+
the fine-grained color tokens
|
|
115
|
+
gs configure set --theme '<json>' merge a whole object (--themeFile <path>);
|
|
116
|
+
--replace overwrites instead of merging
|
|
117
|
+
gs configure set --help every field, its type and legal values
|
|
118
|
+
gs configure upload <kind> <file> icon | logoLight | logoDark (.png/.jpg/.webp)
|
|
119
|
+
gs configure clear <kind> remove an uploaded asset
|
|
120
|
+
|
|
121
|
+
gs connectors [--json] list Maker toggle + custom connectors
|
|
122
|
+
gs connectors add <name> --url <url> [--token <t>] [--profileUrl <u>]
|
|
123
|
+
[--disabled] [--force]
|
|
124
|
+
gs connectors remove <name|id>
|
|
125
|
+
gs connectors enable|disable <name|id>
|
|
126
|
+
gs connectors maker on|off
|
|
127
|
+
gs connectors health [<name|id>] [--json]
|
|
128
|
+
|
|
129
|
+
gs apps <init|build|list|pull|push|publish|unpublish|delete>
|
|
130
|
+
see chat-components.md for the workflow
|
|
131
|
+
```
|