@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.
|
|
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:
|