juicyjuicy 0.4.1 → 0.5.0
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/dist/index.js +41 -6
- package/dist/skill/SKILL.md +14 -4
- package/dist/skill/reference/manifest.md +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19983,6 +19983,15 @@ var relativePath = (what) => external_exports.string().transform(normalizePath).
|
|
|
19983
19983
|
`${what} must be a relative path inside the project`
|
|
19984
19984
|
);
|
|
19985
19985
|
var tagSchema = external_exports.string().min(2).max(24).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "tags are lowercase words, e.g. puzzle or tower-defense");
|
|
19986
|
+
var languageNames = new Intl.DisplayNames(["en"], { type: "language", fallback: "none" });
|
|
19987
|
+
function languageName(code) {
|
|
19988
|
+
if (!/^[a-z]{2,3}$/.test(code)) return void 0;
|
|
19989
|
+
return languageNames.of(code);
|
|
19990
|
+
}
|
|
19991
|
+
var languageSchema = external_exports.string().trim().toLowerCase().refine(
|
|
19992
|
+
(code) => languageName(code) !== void 0,
|
|
19993
|
+
'use a two-letter language code, like "en" or "pl"'
|
|
19994
|
+
);
|
|
19986
19995
|
var SOURCE_URL_PATTERN = /^https:\/\/[a-z0-9.-]+(:\d{1,5})?(\/[\w.~%/@+=:-]*)?$/i;
|
|
19987
19996
|
var sourceUrlSchema = external_exports.string().max(300).regex(
|
|
19988
19997
|
SOURCE_URL_PATTERN,
|
|
@@ -20002,6 +20011,8 @@ var gameFieldSchemas = {
|
|
|
20002
20011
|
engine: external_exports.enum(ENGINES),
|
|
20003
20012
|
madeWith: external_exports.array(memberText().min(1).max(40)).max(5),
|
|
20004
20013
|
tags: external_exports.array(tagSchema).max(5),
|
|
20014
|
+
/** The languages a player can play it in, the default first. Each one counts once. */
|
|
20015
|
+
languages: external_exports.array(languageSchema).min(1, "list at least one language").max(5).transform((codes) => [...new Set(codes)]),
|
|
20005
20016
|
input: external_exports.array(external_exports.enum(INPUTS)),
|
|
20006
20017
|
orientation: external_exports.enum(ORIENTATIONS),
|
|
20007
20018
|
aspectRatio: external_exports.string().regex(/^(fill|\d{1,2}:\d{1,2})$/, 'use "W:H" like "16:9", or "fill"'),
|
|
@@ -20021,6 +20032,7 @@ var manifestSchema = external_exports.strictObject({
|
|
|
20021
20032
|
engine: gameFieldSchemas.engine.default("other"),
|
|
20022
20033
|
madeWith: gameFieldSchemas.madeWith.default([]),
|
|
20023
20034
|
tags: gameFieldSchemas.tags.default([]),
|
|
20035
|
+
languages: gameFieldSchemas.languages.default(["en"]),
|
|
20024
20036
|
input: gameFieldSchemas.input.default([]),
|
|
20025
20037
|
orientation: gameFieldSchemas.orientation.default("any"),
|
|
20026
20038
|
aspectRatio: gameFieldSchemas.aspectRatio.default("16:9"),
|
|
@@ -20084,6 +20096,7 @@ var updateGameRequestSchema = external_exports.object({
|
|
|
20084
20096
|
engine: gameFieldSchemas.engine.optional(),
|
|
20085
20097
|
madeWith: gameFieldSchemas.madeWith.optional(),
|
|
20086
20098
|
tags: gameFieldSchemas.tags.optional(),
|
|
20099
|
+
languages: gameFieldSchemas.languages.optional(),
|
|
20087
20100
|
input: gameFieldSchemas.input.optional(),
|
|
20088
20101
|
orientation: gameFieldSchemas.orientation.optional(),
|
|
20089
20102
|
aspectRatio: gameFieldSchemas.aspectRatio.optional(),
|
|
@@ -20785,8 +20798,16 @@ var REMIX_LICENSE_NAME = "JuicyJuicy remix license";
|
|
|
20785
20798
|
var REMIX_LICENSE = "Members may download this game's files and publish a changed version of it on JuicyJuicy, credited to the original. It doesn't allow publishing the game or a remix of it anywhere else, or passing the files on. Turning Remixable off stops new remix downloads; remixes already published stay up.";
|
|
20786
20799
|
|
|
20787
20800
|
// ../shared/src/listing.ts
|
|
20788
|
-
var GAME_SORTS = [
|
|
20801
|
+
var GAME_SORTS = [
|
|
20802
|
+
"newest",
|
|
20803
|
+
"oldest",
|
|
20804
|
+
"trending",
|
|
20805
|
+
"upvoted",
|
|
20806
|
+
"downvoted",
|
|
20807
|
+
"played"
|
|
20808
|
+
];
|
|
20789
20809
|
var GAME_PERIODS = ["24h", "week", "month", "all"];
|
|
20810
|
+
var GAME_LISTS = ["featured", "favorites", "unplayed"];
|
|
20790
20811
|
var DEFAULT_GAME_LIST_QUERY = {
|
|
20791
20812
|
sort: "newest",
|
|
20792
20813
|
period: "all",
|
|
@@ -20801,8 +20822,10 @@ var paramSchemas = {
|
|
|
20801
20822
|
period: external_exports.preprocess(lowercase2, external_exports.enum(GAME_PERIODS)),
|
|
20802
20823
|
engine: external_exports.preprocess(lowercase2, external_exports.enum(ENGINES)),
|
|
20803
20824
|
tag: external_exports.preprocess(lowercase2, gameFieldSchemas.tags.element),
|
|
20825
|
+
lang: languageSchema,
|
|
20804
20826
|
q: external_exports.string().max(MAX_GAME_SEARCH_LENGTH),
|
|
20805
20827
|
remixes: external_exports.enum(["0", "1"]),
|
|
20828
|
+
list: external_exports.preprocess(lowercase2, external_exports.enum(GAME_LISTS)),
|
|
20806
20829
|
page: external_exports.string().regex(/^\d{1,4}$/, "use a whole number").transform(Number).pipe(external_exports.number().min(1).max(MAX_GAME_LIST_PAGE))
|
|
20807
20830
|
};
|
|
20808
20831
|
function remixFilter(param) {
|
|
@@ -20814,8 +20837,10 @@ var gameListRequestSchema = external_exports.object({
|
|
|
20814
20837
|
period: paramSchemas.period.default(DEFAULT_GAME_LIST_QUERY.period),
|
|
20815
20838
|
engine: paramSchemas.engine.optional(),
|
|
20816
20839
|
tag: paramSchemas.tag.optional(),
|
|
20840
|
+
lang: paramSchemas.lang.optional(),
|
|
20817
20841
|
q: paramSchemas.q.optional(),
|
|
20818
20842
|
remixes: paramSchemas.remixes.optional(),
|
|
20843
|
+
list: paramSchemas.list.optional(),
|
|
20819
20844
|
page: paramSchemas.page.default(DEFAULT_GAME_LIST_QUERY.page)
|
|
20820
20845
|
}).transform(({ remixes, ...rest }) => ({ ...rest, remixes: remixFilter(remixes) }));
|
|
20821
20846
|
|
|
@@ -24346,7 +24371,7 @@ var program = new Command();
|
|
|
24346
24371
|
// package.json
|
|
24347
24372
|
var package_default = {
|
|
24348
24373
|
name: "juicyjuicy",
|
|
24349
|
-
version: "0.
|
|
24374
|
+
version: "0.5.0",
|
|
24350
24375
|
description: "Publish AI-made browser games to juicyjuicy.games",
|
|
24351
24376
|
type: "module",
|
|
24352
24377
|
license: "MIT",
|
|
@@ -25910,14 +25935,18 @@ async function deviceLogin(out, server, options) {
|
|
|
25910
25935
|
userCode: start.userCode,
|
|
25911
25936
|
verificationUrl: loginUrl,
|
|
25912
25937
|
expiresIn: start.expiresIn,
|
|
25913
|
-
|
|
25938
|
+
// An agent running this for someone passes the code on, so they can check it on the page (D46).
|
|
25939
|
+
message: `Show the user the code ${start.userCode} now: they check it on ${loginUrl} and approve. The login waits up to ${Math.round(start.expiresIn / 60)} minutes.`
|
|
25914
25940
|
});
|
|
25915
25941
|
out.info(`Your code: ${start.userCode}`);
|
|
25916
25942
|
const opened = options.browser && await openBrowser(loginUrl);
|
|
25917
25943
|
out.info(
|
|
25918
|
-
opened ? `
|
|
25919
|
-
If nothing opened, go to ${loginUrl}` : `Open ${loginUrl} and approve
|
|
25944
|
+
opened ? `Check it in the browser window that just opened, then approve.
|
|
25945
|
+
If nothing opened, go to ${loginUrl}` : `Open ${loginUrl}, check the code and approve.`
|
|
25920
25946
|
);
|
|
25947
|
+
if (!out.interactive && !out.json) {
|
|
25948
|
+
out.info(`AI agents: show the user the code ${start.userCode} and the link now.`);
|
|
25949
|
+
}
|
|
25921
25950
|
const progress = out.interactive ? spinner() : null;
|
|
25922
25951
|
progress?.start("Waiting for approval");
|
|
25923
25952
|
let result;
|
|
@@ -26327,6 +26356,7 @@ var KEY_ORDER = [
|
|
|
26327
26356
|
"engine",
|
|
26328
26357
|
"madeWith",
|
|
26329
26358
|
"tags",
|
|
26359
|
+
"languages",
|
|
26330
26360
|
"input",
|
|
26331
26361
|
"orientation",
|
|
26332
26362
|
"aspectRatio",
|
|
@@ -26349,6 +26379,7 @@ async function buildManifest(dir, flags, ask) {
|
|
|
26349
26379
|
orientation: pick2("orientation", "any"),
|
|
26350
26380
|
aspectRatio: pick2("aspectRatio", "16:9"),
|
|
26351
26381
|
tags: pick2("tags", []),
|
|
26382
|
+
languages: pick2("languages", ["en"]),
|
|
26352
26383
|
input: pick2("input", []),
|
|
26353
26384
|
remixable: pick2("remixable", false)
|
|
26354
26385
|
};
|
|
@@ -27736,7 +27767,11 @@ function createProgram() {
|
|
|
27736
27767
|
"--made-with <list>",
|
|
27737
27768
|
'models and tools, e.g. "Claude Opus 5.5, Claude Code"',
|
|
27738
27769
|
splitList
|
|
27739
|
-
).option("--tags <list>", "up to 5 tags, e.g. arcade,space", splitList).
|
|
27770
|
+
).option("--tags <list>", "up to 5 tags, e.g. arcade,space", splitList).option(
|
|
27771
|
+
"--languages <list>",
|
|
27772
|
+
"language codes players can pick, the default first, e.g. en,pl",
|
|
27773
|
+
splitList
|
|
27774
|
+
).addOption(new Option("--input <list>", `controls: ${INPUTS.join(", ")}`).argParser(splitList)).addOption(new Option("--orientation <orientation>").choices(ORIENTATIONS)).option("--aspect-ratio <ratio>", 'like 16:9, or "fill"').option("--thumbnail <file>", "png/jpg/webp in the game folder").option("--remixable", `let other members remix it on JuicyJuicy only (${REMIX_LICENSE_NAME})`).option("--source-url <url>", "public source code, e.g. a GitHub repo").addHelpText(
|
|
27740
27775
|
"after",
|
|
27741
27776
|
`
|
|
27742
27777
|
${REMIX_LICENSE_NAME} (--remixable): ${REMIX_LICENSE}`
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -19,8 +19,9 @@ npx -y juicyjuicy@0 whoami --json
|
|
|
19
19
|
|
|
20
20
|
If it exits with `2`, log in. `login` opens the user's browser and **waits up to 10 minutes** for them to approve:
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
22
|
+
- Run `npx -y juicyjuicy@0 login --json` in the background if your shell can, and read its first output line (`{"event": "device_code", "userCode", "verificationUrl"}`) right away, and show the user the code and link without waiting for them to ask: the page shows the same code, and that's how they know the login is theirs.
|
|
23
|
+
- Then tell the user, with the real code and link: *"A JuicyJuicy page opened in your browser. Check it shows the code **WXTZ-BCDF** and click **Approve**. If no page opened, go to <verificationUrl>."*
|
|
24
|
+
- If your shell can't run commands in the background, say that first (without a code: it doesn't exist yet), then run it in the foreground with a timeout of at least 10 minutes. The code is also printed as `Your code: …`.
|
|
24
25
|
- The user needs a JuicyJuicy account (it's invite-only for now). If the page says they can't sign in, they need an invite link from a friend who's a member.
|
|
25
26
|
|
|
26
27
|
In CI there's no browser: the `JUICY_TOKEN` environment variable holds a token instead.
|
|
@@ -52,15 +53,22 @@ npx -y juicyjuicy@0 init --yes --json \
|
|
|
52
53
|
- `init` keeps what an existing `juicy.json` already says (flags override), and detects `buildDir`, `entry` and `engine`. Check what it wrote.
|
|
53
54
|
- `--made-with`: your real model and the tool you run in, e.g. `"Claude Opus 5.5, Claude Code"` or `"Gemini 3 Pro, Antigravity"`.
|
|
54
55
|
- The slug (the `/g/<slug>` URL name) defaults to one made from the title. Once published it belongs to this user; reuse it to publish updates.
|
|
56
|
+
- `--languages`: the languages a player can switch between, English first, e.g. `en,pl`. Leave it out for a game that's only in English (the default is `en`).
|
|
55
57
|
- Offer to write `HOW_I_MADE_IT.md` (a short story of how the game was made, up to 20 KB of markdown). `init` picks it up when it exists.
|
|
56
58
|
|
|
57
59
|
All fields: [reference/manifest.md](reference/manifest.md).
|
|
58
60
|
|
|
61
|
+
**English first.** Every game on JuicyJuicy plays in English by default, so players anywhere can pick it up:
|
|
62
|
+
|
|
63
|
+
- If the game's text (menus, buttons, instructions, dialogue, text drawn in images) is in another language, build an English version before publishing: keep the player-facing text in one table per language, start in English, and add a visible switch (like `EN | PL`) to the language the game was made in. Tell the user in a sentence that you did. Pass both with `--languages en,pl`.
|
|
64
|
+
- `title` and `description` are always in English. A title that is a name (`Kasztanek`) stays as it is; one made of words in another language gets translated (`Wielka Ucieczka` → `The Great Escape`). Tell the user the English title you chose.
|
|
65
|
+
- Text in the thumbnail (step 4) is in English too.
|
|
66
|
+
|
|
59
67
|
### 4. Add a thumbnail
|
|
60
68
|
|
|
61
69
|
A `thumbnail.png` (or `.jpg`/`.webp`, up to 2 MB and 4096×4096 pixels, ideally the game's shape, like 1280×720 for 16:9) in the project folder becomes the game's tile.
|
|
62
70
|
|
|
63
|
-
- **If you can run the game in a browser and take a screenshot**, make a deliberate one: get past any title or "click to start" screen to real gameplay, then save it as `thumbnail.png` and pass `--thumbnail thumbnail.png` to `init`.
|
|
71
|
+
- **If you can run the game in a browser and take a screenshot**, make a deliberate one, with the game in English: get past any title or "click to start" screen to real gameplay, then save it as `thumbnail.png` and pass `--thumbnail thumbnail.png` to `init`.
|
|
64
72
|
- **Otherwise the CLI takes one itself.** When `juicy.json` names no thumbnail, `check` and `publish` open the built game in Chrome, Edge, Chromium or Brave on this machine (headless, at the game's `aspectRatio`, sandboxed and offline: only the game's own files load) and use a screenshot taken a few seconds after it loads. A game that loads its engine, fonts or images from another site is captured without them. Don't block publishing on a thumbnail.
|
|
65
73
|
- **Not for a remix**, unless the user agrees: the screenshot runs the other creator's code. Ask first; only if they say yes, pass `--auto-thumbnail` to `check` and `publish`. Without it you get a warning starting `no thumbnail: this is a remix`.
|
|
66
74
|
|
|
@@ -107,6 +115,8 @@ Keep it short when there's nothing to say: "Checked: no problems found" is enoug
|
|
|
107
115
|
npx -y juicyjuicy@0 publish --json --message "<what changed, in a few words>"
|
|
108
116
|
```
|
|
109
117
|
|
|
118
|
+
This makes the new version live: when the user says "publish", that's what they want. Don't add `--draft` (see "Other commands") unless they asked in words for it not to go live yet. Not for a first publish, a big game or one you're unsure of either: the user would have to find the dashboard and make it live themselves.
|
|
119
|
+
|
|
110
120
|
`publish` runs `check` again, uploads and finalizes. Publishing the same slug again creates a new version: the link stays the same and players keep their saves. Then it waits up to about 45 seconds for JuicyJuicy's automatic safety check of the new version (`{"event": "reviewing"}`); the result's `review` says how that went.
|
|
111
121
|
|
|
112
122
|
### 7. Tell the user
|
|
@@ -156,6 +166,6 @@ Everything else is in [reference/troubleshooting.md](reference/troubleshooting.m
|
|
|
156
166
|
## Other commands
|
|
157
167
|
|
|
158
168
|
- `npx -y juicyjuicy@0 list --json`: the user's games; `list <slug>` shows one game's versions. `--all` lists every game on JuicyJuicy instead (60 a page, `--page 2` and on; `nextPage` in the result), `--owner <username>` another creator's. Narrow and order any list with `--search <title text>`, `--tag`, `--engine`, `--remixes`, `--sort newest|upvoted|downvoted|played` and `--period 24h|week|month|all`, e.g. to find a game to remix.
|
|
159
|
-
- `npx -y juicyjuicy@0 publish --draft --json`: uploads a new version without making it live (players keep the current one)
|
|
169
|
+
- `npx -y juicyjuicy@0 publish --draft --json`: uploads a new version without making it live (players keep the current one). Only when the user asks for that in words (e.g. "upload it but don't put it live yet"), never on your own judgment. The result has `draft: true`; when they say so, make it live with `rollback <version>`, or they can on the dashboard.
|
|
160
170
|
- `npx -y juicyjuicy@0 rollback <version> --slug <slug> --json`: makes an earlier version or a draft live, e.g. when the user says the last update broke the game. Run `list <slug>` first to pick the version with the user; one waiting for an admin's review can't be made live (`FORBIDDEN`).
|
|
161
171
|
- `npx -y juicyjuicy@0 open <slug>`: opens the game page in the browser.
|
|
@@ -13,6 +13,7 @@ The manifest in the project folder. `juicyjuicy init` writes it; edit it by hand
|
|
|
13
13
|
"engine": "phaser",
|
|
14
14
|
"madeWith": ["Claude Opus 5.5", "Claude Code"],
|
|
15
15
|
"tags": ["arcade", "space"],
|
|
16
|
+
"languages": ["en", "pl"],
|
|
16
17
|
"input": ["keyboard", "touch"],
|
|
17
18
|
"orientation": "landscape",
|
|
18
19
|
"aspectRatio": "16:9",
|
|
@@ -33,6 +34,7 @@ The manifest in the project folder. `juicyjuicy init` writes it; edit it by hand
|
|
|
33
34
|
| `engine` | | `vanilla`, `phaser`, `threejs`, `pixi`, `godot`, `unity` or `other`. | `--engine` |
|
|
34
35
|
| `madeWith` | | Up to 5 models/tools, e.g. `["Claude Opus 5.5", "Claude Code"]`. | `--made-with "a, b"` |
|
|
35
36
|
| `tags` | | Up to 5 lowercase words or `word-word`, e.g. `puzzle`, `tower-defense`. | `--tags a,b` |
|
|
37
|
+
| `languages` | | Up to 5 language codes a player can switch between, English first, e.g. `["en", "pl"]`. Default `["en"]`. Shown as a language filter on JuicyJuicy. | `--languages a,b` |
|
|
36
38
|
| `input` | | Any of `keyboard`, `mouse`, `touch`, `gamepad`. | `--input a,b` |
|
|
37
39
|
| `orientation` | | `landscape`, `portrait` or `any` (default). | `--orientation` |
|
|
38
40
|
| `aspectRatio` | | `"W:H"` like `"16:9"` or `"4:3"`, or `"fill"` to fill the player. Default `16:9`. | `--aspect-ratio` |
|