@genex-ai/cli-demo 1.36.17-dev.1066 → 1.36.18-dev.1068

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.36.17-dev.1066",
3
+ "version": "1.36.18-dev.1068",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -179,7 +179,10 @@ each shell is in [references/shells.md](references/shells.md).
179
179
 
180
180
  - `share({ text?, params? })` → `Promise<{ status, url }>`; `status` is
181
181
  `"shared" | "copied" | "dismissed" | "unavailable"`. The SDK builds the
182
- link; the game supplies only its own `params`.
182
+ link; the game supplies only its own `params`. A link whose preview card
183
+ shows the player's own result needs a server of the creator's own, and only
184
+ when the person asks for one: "A result link with its own preview picture"
185
+ in [references/shells.md](references/shells.md).
183
186
  - `copyText(text)`, `keepAwake(on)`, `openExternal(url)`,
184
187
  `saveFile(blob, name)`, `setFullscreen(on)` →
185
188
  `Promise<{ ok: true } | { ok: false, error }>`; `error` is `unsupported` |
@@ -197,6 +197,110 @@ const from = entry.referrer; // "https://example.com", or null
197
197
  - **A value from a link is text anyone can write.** Check its shape before you
198
198
  use it.
199
199
 
200
+ ## A result link with its own preview picture
201
+
202
+ `share()` links to the game's page on Genex, and a link card (X, Telegram,
203
+ Discord, iMessage) shows that page's picture: the game's cover, the same for
204
+ every player. Genex keeps no picture per shared link. A game whose players
205
+ should each share a card with their OWN result ("Sam scored 12,400") needs a
206
+ small server of the creator's own that answers that card and sends people on to
207
+ the game. Build it only when the person asks: it is a server outside Genex that
208
+ they host, pay for and answer for, so say that before you build it. It needs no
209
+ Genex credential; never put one in the game.
210
+
211
+ The flow, with `result` as the parameter (any name that is not reserved):
212
+
213
+ 1. **When the round ends**, the game posts the result and its card picture to
214
+ the creator's server, which stores both and answers a link on its own
215
+ domain: `https://share.example.com/r/<id>`.
216
+ 2. **That link's page** carries the card in its HTML and sends the person on to
217
+ the game's page on Genex with `?result=<id>`.
218
+ 3. **The game opens** from that link, reads `getEntry().params.get("result")`
219
+ and fetches the result to show it ("Beat Sam's 12,400").
220
+
221
+ ```ts
222
+ import { copyText, getEntry, share } from "@genex-ai/embed-sdk";
223
+
224
+ const SHARE_API = "https://share.example.com"; // the creator's server, never a Genex address
225
+ let resultUrl: string | null = null;
226
+
227
+ // 1. When the round ends, before the Share button shows: a click must not wait on the network.
228
+ async function prepareShare(score: number, card: Blob): Promise<void> {
229
+ const body = new FormData();
230
+ body.append("score", String(score));
231
+ body.append("card", card, "card.png");
232
+ try {
233
+ const res = await fetch(`${SHARE_API}/results`, { method: "POST", body });
234
+ resultUrl = res.ok ? (await res.json()).url : null;
235
+ } catch {
236
+ resultUrl = null; // no server: the button falls back to share()
237
+ }
238
+ }
239
+
240
+ shareBtn.onclick = () => {
241
+ const text = "Beat my score";
242
+ if (!resultUrl) return void share({ text }); // the game's Genex page, with its cover
243
+ if (typeof navigator.share === "function") {
244
+ navigator.share({ url: resultUrl, text }).catch((e) => {
245
+ if (e?.name !== "AbortError") showLink(resultUrl!); // AbortError: the player said no
246
+ });
247
+ } else {
248
+ void copyText(resultUrl).then((r) => (r.ok ? showCopied() : showLink(resultUrl!)));
249
+ }
250
+ };
251
+
252
+ // 3. Opened from a result link.
253
+ const id = getEntry().params.get("result");
254
+ if (id && /^[A-Za-z0-9_-]{6,32}$/.test(id)) void showSharedResult(id); // a missing one is ordinary
255
+ ```
256
+
257
+ `share()` takes no link of its own, so the result link goes through
258
+ `navigator.share` where the browser has a sheet (the share row above), and
259
+ `copyText()` where it has none. Keep `share()` as the fallback, so a game
260
+ whose server is down still shares its Genex page.
261
+
262
+ **The card picture.** 1200×630, PNG or JPEG (some cards drop WebP), under
263
+ 1 MB. Draw it on a 2D canvas: `drawImage` the game's canvas right after
264
+ `renderer.render()` in the same frame (a WebGL canvas read later comes back
265
+ blank), then whatever the result card shows on top.
266
+
267
+ **The server's page for `/r/<id>`** answers `200` with HTML like this, every
268
+ player-sent value HTML-escaped:
269
+
270
+ ```html
271
+ <!doctype html>
272
+ <meta charset="utf-8">
273
+ <title>Sam scored 12,400 in My Game</title>
274
+ <meta property="og:title" content="Sam scored 12,400">
275
+ <meta property="og:description" content="Can you beat it? Play My Game in your browser.">
276
+ <meta property="og:image" content="https://share.example.com/cards/k3f9x2.png">
277
+ <meta property="og:image:width" content="1200">
278
+ <meta property="og:image:height" content="630">
279
+ <meta property="og:url" content="https://share.example.com/r/k3f9x2">
280
+ <meta name="twitter:card" content="summary_large_image">
281
+ <meta http-equiv="refresh" content="0;url=https://dev.genex.games/my-game?result=k3f9x2">
282
+ <script>location.replace("https://dev.genex.games/my-game?result=k3f9x2");</script>
283
+ ```
284
+
285
+ What breaks it:
286
+
287
+ - **A server redirect.** Never answer `/r/<id>` with a 301 or 302: link
288
+ crawlers follow it and take the card of the Genex page, the cover. Send the
289
+ person on with the meta refresh and the script above.
290
+ - **`og:url` naming the Genex page.** Some crawlers read the card from the
291
+ address `og:url` names. It is the result link itself.
292
+ - **Tags added by a script.** Crawlers run no JavaScript; the tags must be in
293
+ the HTML the server sends.
294
+ - **CORS.** The game posts from its own origins: its live address and its
295
+ preview address (`npx genex preview` prints both), its own domain if it has
296
+ one, and the local dev server. The server allows exactly those.
297
+
298
+ The server answers for what its cards show: anyone can post any picture to it.
299
+ Accept only PNG or JPEG under the size cap, cap how many a player or an address
300
+ may post a day, and keep a way to take one down. A card a social network
301
+ already shows is read again later, so a link whose picture is gone keeps
302
+ answering with its tags and the game's cover as `og:image`, never a 404.
303
+
200
304
  ## The frame, exactly
201
305
 
202
306
  What the Genex page gives the frame a game runs in, by its attribute names: