juicyjuicy 0.1.0 → 0.2.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/README.md +4 -5
- package/dist/index.js +83 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
Publish browser games you make with Claude to [juicyjuicy.games](https://juicyjuicy.games), and get a link your friends can play.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx juicyjuicy
|
|
7
|
-
npx juicyjuicy login # approve in the browser once
|
|
6
|
+
npx juicyjuicy setup # once: installs the Claude Code skill and logs you in (approve in the browser)
|
|
8
7
|
```
|
|
9
8
|
|
|
10
|
-
Then ask Claude: *"Publish this game to JuicyJuicy."* Or do it yourself:
|
|
9
|
+
Then ask Claude: *"Publish this game to JuicyJuicy."* Or paste *"Run npx juicyjuicy setup, then publish this game to JuicyJuicy."* into Claude Code and skip the step above. Or do it yourself:
|
|
11
10
|
|
|
12
11
|
```bash
|
|
13
|
-
npx juicyjuicy
|
|
12
|
+
npx juicyjuicy publish # upload and print the link (first time: logs in, writes juicy.json)
|
|
13
|
+
npx juicyjuicy init # create or edit juicy.json
|
|
14
14
|
npx juicyjuicy check # validate without uploading
|
|
15
|
-
npx juicyjuicy publish # upload and print the link
|
|
16
15
|
npx juicyjuicy list # your games and their versions
|
|
17
16
|
npx juicyjuicy open # open the game page
|
|
18
17
|
```
|
package/dist/index.js
CHANGED
|
@@ -23436,7 +23436,7 @@ var program = new Command();
|
|
|
23436
23436
|
// package.json
|
|
23437
23437
|
var package_default = {
|
|
23438
23438
|
name: "juicyjuicy",
|
|
23439
|
-
version: "0.
|
|
23439
|
+
version: "0.2.0",
|
|
23440
23440
|
description: "Publish AI-made browser games to juicyjuicy.games",
|
|
23441
23441
|
type: "module",
|
|
23442
23442
|
license: "MIT",
|
|
@@ -24637,6 +24637,13 @@ async function requireAuth(server, env = process.env) {
|
|
|
24637
24637
|
// src/commands/auth.ts
|
|
24638
24638
|
async function login(out, options) {
|
|
24639
24639
|
const server = apiUrl();
|
|
24640
|
+
const user = await deviceLogin(out, server, options);
|
|
24641
|
+
out.result(
|
|
24642
|
+
{ user, apiUrl: server },
|
|
24643
|
+
`Logged in as @${user.username}${server === DEFAULT_API_URL ? "" : ` on ${server}`}.`
|
|
24644
|
+
);
|
|
24645
|
+
}
|
|
24646
|
+
async function deviceLogin(out, server, options) {
|
|
24640
24647
|
if (process.env.JUICY_TOKEN) {
|
|
24641
24648
|
out.warn("JUICY_TOKEN is set, so it will still win over the login you save now.");
|
|
24642
24649
|
}
|
|
@@ -24670,10 +24677,14 @@ If nothing opened, go to ${start.verificationUrlComplete}` : `Open ${start.verif
|
|
|
24670
24677
|
username: result.user.username,
|
|
24671
24678
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24672
24679
|
});
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
);
|
|
24680
|
+
return result.user;
|
|
24681
|
+
}
|
|
24682
|
+
async function ensureLogin(out, server) {
|
|
24683
|
+
const auth = await resolveAuth(server);
|
|
24684
|
+
if (auth || !out.interactive) return auth ?? requireAuth(server);
|
|
24685
|
+
out.info("You're not logged in yet, so let's do that first.");
|
|
24686
|
+
await deviceLogin(out, server, { browser: true });
|
|
24687
|
+
return requireAuth(server);
|
|
24677
24688
|
}
|
|
24678
24689
|
async function pollForToken(api, start) {
|
|
24679
24690
|
let interval = start.interval;
|
|
@@ -24759,7 +24770,7 @@ async function loadManifest(dir) {
|
|
|
24759
24770
|
const raw = await readRawManifest(dir);
|
|
24760
24771
|
if (!raw) {
|
|
24761
24772
|
throw new CliError("INVALID_MANIFEST", `There's no ${MANIFEST_FILE} in ${dir}.`, {
|
|
24762
|
-
hint: "Run `npx juicyjuicy init` to create one."
|
|
24773
|
+
hint: "Run `npx juicyjuicy init` to create one. Claude: `npx -y juicyjuicy@latest setup` installs the JuicyJuicy skill, which has the whole publishing procedure."
|
|
24763
24774
|
});
|
|
24764
24775
|
}
|
|
24765
24776
|
const parsed = manifestSchema.safeParse(raw);
|
|
@@ -25068,7 +25079,13 @@ function splitList(value) {
|
|
|
25068
25079
|
|
|
25069
25080
|
// src/commands/project.ts
|
|
25070
25081
|
async function init(out, dir, flags) {
|
|
25071
|
-
const
|
|
25082
|
+
const { path: path2, created, manifest } = await createManifest(out, resolve2(dir), flags);
|
|
25083
|
+
out.result(
|
|
25084
|
+
{ path: path2, created, manifest },
|
|
25085
|
+
`${created ? "Created" : "Updated"} ${relative2(process.cwd(), path2) || path2}. Next: npx juicyjuicy check`
|
|
25086
|
+
);
|
|
25087
|
+
}
|
|
25088
|
+
async function createManifest(out, root, flags) {
|
|
25072
25089
|
const ask = out.interactive ? async ({ message, initial, validate: validate2 }) => {
|
|
25073
25090
|
const answer = await text({
|
|
25074
25091
|
message,
|
|
@@ -25080,11 +25097,7 @@ async function init(out, dir, flags) {
|
|
|
25080
25097
|
} : void 0;
|
|
25081
25098
|
const { manifest, created, detected } = await buildManifest(root, flags, ask);
|
|
25082
25099
|
for (const warning of detected.warnings) out.warn(warning);
|
|
25083
|
-
|
|
25084
|
-
out.result(
|
|
25085
|
-
{ path: path2, created, manifest },
|
|
25086
|
-
`${created ? "Created" : "Updated"} ${relative2(process.cwd(), path2) || path2}. Next: npx juicyjuicy check`
|
|
25087
|
-
);
|
|
25100
|
+
return { path: await writeManifest(root, manifest), created, manifest };
|
|
25088
25101
|
}
|
|
25089
25102
|
async function check2(out, dir, options) {
|
|
25090
25103
|
const result = await checkProject(resolve2(dir), options);
|
|
@@ -25119,7 +25132,7 @@ function formatBytes(bytes) {
|
|
|
25119
25132
|
}
|
|
25120
25133
|
|
|
25121
25134
|
// src/commands/publish.ts
|
|
25122
|
-
import { join as join5, resolve as resolve3 } from "path";
|
|
25135
|
+
import { join as join5, relative as relative3, resolve as resolve3 } from "path";
|
|
25123
25136
|
|
|
25124
25137
|
// src/upload.ts
|
|
25125
25138
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -25187,6 +25200,11 @@ async function uploadFiles(tasks, options = {}) {
|
|
|
25187
25200
|
// src/commands/publish.ts
|
|
25188
25201
|
async function publish(out, dir, options) {
|
|
25189
25202
|
const root = resolve3(dir);
|
|
25203
|
+
if (out.interactive && !await readRawManifest(root)) {
|
|
25204
|
+
out.info("This game has no juicy.json yet, so let's make one.");
|
|
25205
|
+
const { path: path2 } = await createManifest(out, root, {});
|
|
25206
|
+
out.success(`Created ${relative3(process.cwd(), path2) || path2}`);
|
|
25207
|
+
}
|
|
25190
25208
|
const checked = await checkProject(root, options);
|
|
25191
25209
|
for (const warning of checked.warnings) out.warn(warning);
|
|
25192
25210
|
if (checked.issues.length > 0) throw checkFailed(checked);
|
|
@@ -25197,7 +25215,7 @@ async function publish(out, dir, options) {
|
|
|
25197
25215
|
totalBytes: checked.totalBytes
|
|
25198
25216
|
});
|
|
25199
25217
|
const server = apiUrl();
|
|
25200
|
-
const auth = await
|
|
25218
|
+
const auth = await ensureLogin(out, server);
|
|
25201
25219
|
const api = new ApiClient(server, auth.token);
|
|
25202
25220
|
const { thumbnail, howItWasMade } = checked;
|
|
25203
25221
|
const request = {
|
|
@@ -25298,6 +25316,9 @@ async function open2(out, slug, options) {
|
|
|
25298
25316
|
out.result({ url: url2, opened }, opened ? `Opened ${url2}` : url2);
|
|
25299
25317
|
}
|
|
25300
25318
|
|
|
25319
|
+
// src/commands/setup.ts
|
|
25320
|
+
import { join as join7 } from "path";
|
|
25321
|
+
|
|
25301
25322
|
// src/commands/skill.ts
|
|
25302
25323
|
import { existsSync as existsSync2 } from "fs";
|
|
25303
25324
|
import { cp, rm as rm2 } from "fs/promises";
|
|
@@ -25317,15 +25338,51 @@ function defaultSkillTarget(env = process.env) {
|
|
|
25317
25338
|
return join6(env.CLAUDE_CONFIG_DIR?.trim() || join6(homedir2(), ".claude"), "skills", SKILL_NAME);
|
|
25318
25339
|
}
|
|
25319
25340
|
async function installSkill(out, options) {
|
|
25320
|
-
const
|
|
25321
|
-
|
|
25322
|
-
|
|
25323
|
-
|
|
25341
|
+
const { path: path2, updated } = await copySkill(options);
|
|
25342
|
+
out.result(
|
|
25343
|
+
{ path: path2, updated },
|
|
25344
|
+
`${updated ? "Updated" : "Installed"} the ${SKILL_NAME} skill in ${path2}. In Claude Code, say "publish this game to JuicyJuicy".`
|
|
25345
|
+
);
|
|
25346
|
+
}
|
|
25347
|
+
async function copySkill(options) {
|
|
25348
|
+
const path2 = options.dir ? resolve4(options.dir, SKILL_NAME) : defaultSkillTarget();
|
|
25349
|
+
const updated = existsSync2(path2);
|
|
25350
|
+
await rm2(path2, { recursive: true, force: true });
|
|
25351
|
+
await cp(skillSourceDir(), path2, { recursive: true });
|
|
25352
|
+
return { path: path2, updated };
|
|
25353
|
+
}
|
|
25354
|
+
|
|
25355
|
+
// src/commands/setup.ts
|
|
25356
|
+
async function setup(out, options) {
|
|
25357
|
+
const skill = await copySkill(options);
|
|
25358
|
+
out.event("skill_installed", skill);
|
|
25359
|
+
out.success(`${skill.updated ? "Updated" : "Installed"} the Claude skill in ${skill.path}`);
|
|
25360
|
+
const server = apiUrl();
|
|
25361
|
+
let user = await currentUser(server);
|
|
25362
|
+
if (user) out.success(`Already logged in as @${user.username}`);
|
|
25363
|
+
else user = await deviceLogin(out, server, options);
|
|
25364
|
+
const prompt = "publish this game to JuicyJuicy";
|
|
25324
25365
|
out.result(
|
|
25325
|
-
{
|
|
25326
|
-
|
|
25366
|
+
{
|
|
25367
|
+
user,
|
|
25368
|
+
apiUrl: server,
|
|
25369
|
+
skill,
|
|
25370
|
+
next: `Read ${join7(skill.path, "SKILL.md")} and follow it to publish the game.`
|
|
25371
|
+
},
|
|
25372
|
+
`All set, @${user.username}. Open Claude Code in your game's folder and say "${prompt}". (If Claude doesn't know the ${SKILL_NAME} skill yet, start a new session.)`
|
|
25327
25373
|
);
|
|
25328
25374
|
}
|
|
25375
|
+
async function currentUser(server) {
|
|
25376
|
+
const auth = await resolveAuth(server);
|
|
25377
|
+
if (!auth) return null;
|
|
25378
|
+
try {
|
|
25379
|
+
const { user } = await new ApiClient(server, auth.token).get("/me");
|
|
25380
|
+
return { username: user.username, name: user.name };
|
|
25381
|
+
} catch (error62) {
|
|
25382
|
+
if (error62 instanceof CliError && error62.code === "UNAUTHORIZED") return null;
|
|
25383
|
+
throw error62;
|
|
25384
|
+
}
|
|
25385
|
+
}
|
|
25329
25386
|
|
|
25330
25387
|
// src/program.ts
|
|
25331
25388
|
function createProgram() {
|
|
@@ -25333,6 +25390,7 @@ function createProgram() {
|
|
|
25333
25390
|
"after",
|
|
25334
25391
|
"\nEnvironment:\n JUICY_TOKEN token to use instead of the saved login (CI)\n JUICY_API_URL server to talk to (default https://juicyjuicy.games)"
|
|
25335
25392
|
);
|
|
25393
|
+
command(program2, "setup", "Set up once: install the Claude skill and log in").option("--dir <dir>", "install the skill into this skills folder, e.g. .claude/skills").option("--no-browser", "don't open the browser, just print the link").action(run((out, options) => setup(out, options)));
|
|
25336
25394
|
command(program2, "login", "Log in through your browser (device code) and save a token").option("--no-browser", "don't open the browser, just print the link").action(run((out, options) => login(out, options)));
|
|
25337
25395
|
command(program2, "whoami", "Show who you're logged in as").action(run((out) => whoami(out)));
|
|
25338
25396
|
command(program2, "logout", "Forget and revoke the saved token").action(run((out) => logout(out)));
|
|
@@ -25344,7 +25402,11 @@ function createProgram() {
|
|
|
25344
25402
|
command(program2, "check", "Check juicy.json and the build folder without uploading").argument("[dir]", "game folder", ".").option("--include-source-maps", "upload *.map files too").action(
|
|
25345
25403
|
run((out, options, dir) => check2(out, dir, options))
|
|
25346
25404
|
);
|
|
25347
|
-
command(
|
|
25405
|
+
command(
|
|
25406
|
+
program2,
|
|
25407
|
+
"publish",
|
|
25408
|
+
"Check, upload and publish the game; prints its link (first time: logs in, writes juicy.json)"
|
|
25409
|
+
).argument("[dir]", "game folder", ".").option("-m, --message <text>", "note for the version history, e.g. what changed").option("--include-source-maps", "upload *.map files too").addOption(
|
|
25348
25410
|
new Option("--source <source>", "where this publish comes from").choices(PUBLISH_SOURCES).hideHelp()
|
|
25349
25411
|
).action(run((out, options, dir) => publish(out, dir, options)));
|
|
25350
25412
|
command(program2, "list", "List your games, or one game's versions").argument("[slug]", "show this game's versions").action(run((out, _options, slug) => list(out, slug)));
|