@greatstore/cli 0.1.0 → 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.
@@ -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,121 @@
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
+ theme '<json>' (--themeFile <path>),
106
+ cspScriptHosts a,b, cspConnectHosts a,b
107
+ ("" clears a field)
108
+ gs configure upload <kind> <file> icon | logoLight | logoDark (.png/.jpg/.webp)
109
+ gs configure clear <kind> remove an uploaded asset
110
+
111
+ gs connectors [--json] list Maker toggle + custom connectors
112
+ gs connectors add <name> --url <url> [--token <t>] [--profileUrl <u>]
113
+ [--disabled] [--force]
114
+ gs connectors remove <name|id>
115
+ gs connectors enable|disable <name|id>
116
+ gs connectors maker on|off
117
+ gs connectors health [<name|id>] [--json]
118
+
119
+ gs apps <init|build|list|pull|push|publish|unpublish|delete>
120
+ see chat-components.md for the workflow
121
+ ```