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.
Files changed (3) hide show
  1. package/README.md +4 -5
  2. package/dist/index.js +83 -21
  3. 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 skill install # teach Claude Code to publish (or use the plugin, see the docs)
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 init # write juicy.json
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.1.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
- out.result(
24674
- { user: result.user, apiUrl: server },
24675
- `Logged in as @${result.user.username}${server === "https://juicyjuicy.games" ? "" : ` on ${server}`}.`
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 root = resolve2(dir);
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
- const path2 = await writeManifest(root, manifest);
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 requireAuth(server);
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 target = options.dir ? resolve4(options.dir, SKILL_NAME) : defaultSkillTarget();
25321
- const updated = existsSync2(target);
25322
- await rm2(target, { recursive: true, force: true });
25323
- await cp(skillSourceDir(), target, { recursive: true });
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
- { path: target, updated },
25326
- `${updated ? "Updated" : "Installed"} the ${SKILL_NAME} skill in ${target}. Restart Claude, then ask it to "publish this game to JuicyJuicy".`
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(program2, "publish", "Check, upload and publish the game; prints its link").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(
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)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juicyjuicy",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Publish AI-made browser games to juicyjuicy.games",
5
5
  "type": "module",
6
6
  "license": "MIT",