@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.
@@ -0,0 +1,58 @@
1
+ # Product FAQ accordion
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
+ faqs: {
15
+ type: "array", minItems: 3, maxItems: 5,
16
+ items: {
17
+ type: "object",
18
+ properties: {
19
+ question: { type: "string", maxLength: 90 },
20
+ answer: { type: "string", maxLength: 300 },
21
+ },
22
+ required: ["question", "answer"],
23
+ },
24
+ },
25
+ },
26
+ required: ["faqs"],
27
+ },
28
+ `Generate the questions shoppers most plausibly have before buying the ` +
29
+ `product "${productName}", with accurate answers grounded in the real ` +
30
+ `product details and store policies. Skip any question the store data ` +
31
+ `can't answer confidently.`
32
+ );
33
+
34
+ const wrap = document.getElementById("gs-faq");
35
+ for (const { question, answer } of data.faqs) {
36
+ const details = document.createElement("details");
37
+ const summary = document.createElement("summary");
38
+ summary.textContent = question;
39
+ const p = document.createElement("p");
40
+ p.textContent = answer;
41
+ details.append(summary, p);
42
+ wrap.append(details);
43
+ }
44
+ wrap.hidden = false;
45
+ ```
46
+
47
+ Engagement bonus — append a hand-off row so unanswered questions become
48
+ conversations:
49
+
50
+ ```js
51
+ const ask = document.createElement("button");
52
+ ask.type = "button";
53
+ ask.textContent = "Have a different question? Ask us";
54
+ ask.addEventListener("click", () =>
55
+ window.GreatStore.sendMessage(`I have a question about "${productName}".`)
56
+ );
57
+ wrap.append(ask);
58
+ ```
@@ -0,0 +1,35 @@
1
+ # AI agents and the store's MCP endpoint
2
+
3
+ Every GreatStore store publishes a standard MCP server card, and the
4
+ platform runs one MCP endpoint for coding agents — no authentication
5
+ required for either:
6
+
7
+ | URL | What it is |
8
+ |---|---|
9
+ | `https://<slug>.greatstore.ai/.well-known/mcp/server-card.json` | Standard MCP server card — machine-readable discovery document for the store. |
10
+ | `https://admin.greatstore.ai/mcp` | A **documentation server for coding agents**: its tools return usage docs for the `gs` CLI. Same URL for every store. |
11
+
12
+ ## CLI docs for coding agents — `/mcp`
13
+
14
+ A stateless HTTP MCP whose tools hand back markdown documentation for `gs`
15
+ CLI commands.
16
+
17
+ ```
18
+ claude mcp add --transport http greatstore-admin https://admin.greatstore.ai/mcp
19
+ ```
20
+
21
+ Useful when a coding agent is shipping chat components
22
+ ([chat-components.md](chat-components.md)) and needs the exact command for
23
+ the next step. If this skill is installed, the agent already has the
24
+ workflow — the MCP is the self-serve alternative for agents that don't.
25
+
26
+ ## What to use when
27
+
28
+ - **Building the merchant's site** → this skill's other references (the SDK,
29
+ structured content, page tools).
30
+ - **A coding agent shipping chat components** → the `gs` CLI, with `/mcp`
31
+ as its built-in documentation.
32
+ - **Reading or changing store settings** (origin allowlists, CSP hosts, MCP
33
+ connectors, brand config) → the `gs` CLI's admin commands —
34
+ [store-admin.md](store-admin.md), including which changes are safe to make
35
+ without asking the merchant.
@@ -0,0 +1,224 @@
1
+ # Custom chat components — authoring with the `gs` CLI
2
+
3
+ Remote components are React components the assistant renders **inside the
4
+ conversation** — product configurators, quizzes, size guides, booking forms,
5
+ anything richer than text. Each component is an AI-callable tool: the
6
+ manifest's `description` tells the assistant *when* to show it, its
7
+ `inputSchema` declares the props the assistant fills in, and a `displayMode`
8
+ picks where it appears.
9
+
10
+ Authoring requires store-owner access (`gs login` signs in with the store
11
+ owner's account).
12
+
13
+ ## Workflow
14
+
15
+ ```
16
+ npm install -g @greatstore/cli # or npx @greatstore/cli <command>
17
+ gs login # browser sign-in
18
+ gs apps init --store my-store # scaffold a project root
19
+ cd <project> && npm install
20
+ gs apps init size_guide # scaffold components/size_guide/
21
+ # … edit components/size_guide/{component.tsx,manifest.json} …
22
+ gs apps build # bundle every component
23
+ gs apps push # upload changed components as drafts
24
+ gs apps publish size_guide # promote to live
25
+ ```
26
+
27
+ `gs apps list` shows what's deployed (with dashboard links); `gs apps pull`
28
+ round-trips remote components back to disk. `gs apps push` hashes components
29
+ and only uploads what changed. (The subcommands also work at the top level —
30
+ `gs push` == `gs apps push`.)
31
+
32
+ The scaffold writes an `AGENTS.md` into the project (with `CLAUDE.md` /
33
+ `GEMINI.md` symlinked) containing the complete design rules and brand
34
+ variable table — your coding agent picks it up automatically when working in
35
+ the project. `https://admin.greatstore.ai/mcp` serves the same CLI docs to
36
+ agents over MCP (see [agents-and-mcp.md](agents-and-mcp.md)).
37
+
38
+ ## `manifest.json`
39
+
40
+ ```json
41
+ {
42
+ "name": "size_guide",
43
+ "displayName": "Size guide",
44
+ "description": "Interactive size guide. Show when the shopper asks about sizing or fit for apparel.",
45
+ "displayMode": "inline",
46
+ "inputSchema": {
47
+ "type": "object",
48
+ "properties": {
49
+ "productName": { "type": "string" },
50
+ "category": { "type": "string" }
51
+ },
52
+ "required": ["productName"]
53
+ }
54
+ }
55
+ ```
56
+
57
+ | Field | Meaning |
58
+ |---|---|
59
+ | `name` | Tool name, snake_case (`^[a-z][a-z0-9_]*$`), matches the folder under `components/`. |
60
+ | `displayName` | Friendly label shown in chat UI. |
61
+ | `description` | **Load-bearing** — how the assistant decides when to render the component. Say what it shows *and* when to use it, like any good tool description. |
62
+ | `displayMode` | Where it renders — see below. |
63
+ | `inputSchema` | JSON Schema for the props the assistant fills. Keep it tight; required fields the AI can't infer cause bad calls. |
64
+ | `async` | Set `true` for backend-backed components (see Async below). |
65
+
66
+ ### Display modes
67
+
68
+ - `inline` — a bubble inside the chat transcript; persists with the message
69
+ log.
70
+ - `over-input` — floats above the chat input (like a question overlay);
71
+ cleared by the next user turn or an explicit close.
72
+ - `fullscreen` — takes over the full preview surface; persists until the
73
+ next widget-emitting tool call or an explicit close.
74
+
75
+ ## The component contract
76
+
77
+ `component.tsx` default-exports a React component. Its props are the
78
+ `inputSchema` fields the assistant filled, plus six GreatStore-injected
79
+ lifecycle props (always present):
80
+
81
+ | Prop | What it does |
82
+ |---|---|
83
+ | `onSendMessage(text)` | Send text into the chat as if the shopper typed it — lets the component drive the conversation ("Selected size M, what's the return policy?"). |
84
+ | `onCallTool(name, args)` | Chain into another remote-component tool by name. |
85
+ | `onUpdateModelContext(context)` | Inject **invisible** background context for the model — the variant the shopper selected, the options they configured, the step they're on. Replaces the prior value (never appends); pass `""` to clear. Read on the next chat turn. Use this instead of `onSendMessage` when the assistant should *know* state without a message appearing in the transcript. |
86
+ | `onShowLightbox({ src, originRect? })` | Expand an image in the chat's shared full-screen lightbox — an on-brand zoom overlay you can't render yourself (your component is boxed inside its own bounds and shadow root). Pass the image `src`; for a smooth zoom, also pass the clicked element's `getBoundingClientRect()` as `originRect` (omit it and the image grows from the viewport centre). |
87
+ | `onGenerateStructuredContent(schema, prompt, fallback)` | Ask the store's assistant for content matching a JSON Schema (or Zod schema) and get back the generated object. Personalized to the shopper. Works the same in the conversation on the storefront and every embed — no `window.GreatStore` needed. Best for async components that build their render from a generated payload. `fallback` is **required**: a schema-shaped object you provide that the component preview renders (no live store there), validated against the schema — a mismatch throws. |
88
+ | `onClose()` | Dismiss the host slot. `over-input` clears the overlay, `fullscreen` reverts the pane, `inline` is a no-op. |
89
+
90
+ Reach for `onUpdateModelContext` when the shopper changes something inside
91
+ the component (picks a size, configures a build, advances a quiz) and you
92
+ want the assistant to factor it into the *next* thing they ask — without
93
+ spamming the chat with a visible "I selected M" message. Use `onSendMessage`
94
+ when you actually want a turn to happen now.
95
+
96
+ Reach for `onShowLightbox` whenever your component shows imagery the shopper
97
+ might want to inspect closely — product photos, swatches, a size chart. A
98
+ thumbnail's `onClick` handler is the natural place to call it. Don't build
99
+ your own full-screen modal: a remote component is sandboxed inside its own
100
+ bounds and shadow root, so a self-rendered overlay can't cover the chat. The
101
+ shared lightbox escapes those bounds and themes itself from the store's CSS
102
+ variables.
103
+
104
+ ```tsx
105
+ import React from "react";
106
+
107
+ interface Props {
108
+ productName: string;
109
+ category?: string;
110
+ onSendMessage: (text: string) => void;
111
+ onCallTool: (name: string, args: Record<string, unknown>) => void;
112
+ onUpdateModelContext: (context: string) => void;
113
+ onShowLightbox: (options: { src: string; originRect?: DOMRect }) => void;
114
+ onGenerateStructuredContent: <T = unknown>(
115
+ schema: object,
116
+ prompt: string,
117
+ fallback: T,
118
+ ) => Promise<T>;
119
+ onClose: () => void;
120
+ }
121
+
122
+ export default function SizeGuide({
123
+ productName,
124
+ onSendMessage,
125
+ onUpdateModelContext,
126
+ onShowLightbox,
127
+ }: Props) {
128
+ return (
129
+ <div
130
+ style={{
131
+ padding: "1em",
132
+ border: "1px solid var(--color-border-default)",
133
+ borderRadius: "var(--radius-lg)",
134
+ background: "var(--color-surface)",
135
+ color: "var(--color-foreground)",
136
+ fontFamily: "var(--font-primary)",
137
+ }}
138
+ >
139
+ {/* … sizes for {productName} … */}
140
+ <img
141
+ src={`/size-charts/${productName}.png`}
142
+ alt={`${productName} size chart`}
143
+ style={{ cursor: "zoom-in", width: "100%" }}
144
+ onClick={(e) =>
145
+ onShowLightbox({
146
+ src: `/size-charts/${productName}.png`,
147
+ originRect: e.currentTarget.getBoundingClientRect(),
148
+ })
149
+ }
150
+ />
151
+ <button
152
+ onClick={() =>
153
+ // Silent: the assistant now knows the pick for the shopper's
154
+ // next question, with nothing added to the transcript.
155
+ onUpdateModelContext(`Shopper selected size M of "${productName}".`)
156
+ }
157
+ >
158
+ Select size M
159
+ </button>
160
+ <button onClick={() => onSendMessage(`Size M of "${productName}" — is it in stock?`)}>
161
+ Ask about size M
162
+ </button>
163
+ </div>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ## Design rules (non-negotiable)
169
+
170
+ Components render inside arbitrary publisher pages *and* the GreatStore
171
+ storefront; you control neither the host's root font size nor its colors.
172
+
173
+ 1. **Size in `em`, never `rem`** — `rem` resolves against the host page's
174
+ root font size, which is arbitrary (`html { font-size: 8px }` breaks every
175
+ `rem` dimension). `em` stays self-consistent anywhere. Borders may stay
176
+ `px`.
177
+ 2. **Never hardcode colors, fonts, or radii** — read the brand CSS variables
178
+ GreatStore injects (`--color-primary`, `--color-surface`,
179
+ `--color-foreground`, `--color-border-default`, `--font-primary`,
180
+ `--radius-lg`, …) so the component restyles itself with the store's
181
+ theme. The scaffolded `AGENTS.md` has the full variable table.
182
+
183
+ ## Async components (backend-backed data)
184
+
185
+ If a component must load data before it can render correctly, don't render a
186
+ shell and fetch in `useEffect` — set `"async": true` in the manifest and
187
+ export an **async** default. GreatStore shows its own loading state, awaits
188
+ your promise, and renders what it resolves to.
189
+
190
+ A thrown error is a **retry signal**: the assistant sees it and usually
191
+ re-calls the tool. So only throw when a *different* call could help:
192
+
193
+ 1. Validate the AI-passed props first and throw on bad input — the AI can
194
+ fix the args and retry. (Don't validate the API's *output* and throw: the
195
+ AI can't fix your backend, it'll just loop.)
196
+ 2. Throw on failures where retrying differently could succeed, and say what
197
+ to change (e.g. empty search → `"no results for X — try a broader keyword"`).
198
+ 3. For idempotent failures (500, timeout, missing record) render a graceful
199
+ fallback instead of throwing — re-running the same call changes nothing.
200
+
201
+ ```tsx
202
+ export default async function Results(props: Props) {
203
+ if (!props.query?.trim()) throw new Error("missing required prop: query");
204
+ const res = await fetch(`/api/search?q=${encodeURIComponent(props.query)}`);
205
+ if (res.ok) {
206
+ const { results } = await res.json();
207
+ if (results.length === 0)
208
+ throw new Error(`no results for "${props.query}" — try a broader keyword`);
209
+ return <ul>{/* render results */}</ul>;
210
+ }
211
+ return <p>Couldn't load results right now.</p>; // idempotent: don't throw
212
+ }
213
+ ```
214
+
215
+ ## When to build a component vs. the other facets
216
+
217
+ - Content for the **merchant's page** → `window.GreatStore.generateStructuredContent`
218
+ ([structured-content.md](structured-content.md)). Inside a chat component, use
219
+ the injected `onGenerateStructuredContent` prop instead — same idea, but
220
+ personalized to the shopper and available on every surface without the global.
221
+ - Letting the assistant **act on the page** → WebMCP page tools
222
+ ([embed-api.md](embed-api.md)).
223
+ - Rich, interactive UI **inside the conversation itself**, available on the
224
+ storefront and every embed without page changes → a chat component.