@pixelvibing/cli 0.38.2 → 0.39.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 CHANGED
@@ -21,7 +21,7 @@ Each step it finds done already is skipped.
21
21
  |---|---|
22
22
  | `login`, `logout`, `whoami` | Keep an account token for this machine in the system's credential store, or remove it |
23
23
  | `init`, `link` | Make a directory a local project, and name the platform project it generates for |
24
- | `generate object\|creature\|level-design` | Make art on the platform, after saying its cost in credits, then pull it |
24
+ | `generate object\|creature\|level-design` | Make art on the platform, after saying what it uses of the account's limit, then pull it |
25
25
  | `list`, `pull` | List the project's assets by kind, and pull one by id or name into `pixelvibing/out/` |
26
26
  | `guide pull\|push\|revise\|show\|versions` | Read and write the project's style guide |
27
27
  | `seal` | Seal `pixelvibing/out/` into `pixelvibing/build/assets.eac` |
@@ -525,18 +525,6 @@ class TooFewCredits extends Error {
525
525
  this.credits = credits;
526
526
  }
527
527
  }
528
- function costIn(prices, kind) {
529
- const price = prices.kinds[kind];
530
- if (price === void 0) return null;
531
- return "credits" in price ? price.credits : price.upTo;
532
- }
533
- function costWords(prices, kind) {
534
- const price = prices.kinds[kind];
535
- if (price === void 0) return null;
536
- const most = "credits" in price ? price.credits : price.upTo;
537
- const credits = `${most} credit${most === 1 ? "" : "s"}`;
538
- return "credits" in price ? credits : `up to ${credits}, what it does not use given back`;
539
- }
540
528
  const id = (value) => encodeURIComponent(value);
541
529
  function createWebApi(fetcher = (input, init2) => fetch(input, init2), base = API_BASE) {
542
530
  const call = createCaller(fetcher, base);
@@ -627,8 +615,10 @@ function createWebApi(fetcher = (input, init2) => fetch(input, init2), base = AP
627
615
  },
628
616
  billing: (before = null) => json2(`/billing${before === null ? "" : `?before=${id(before)}`}`),
629
617
  prices: () => json2("/billing/prices"),
630
- checkoutPack: async (packId) => (await json2(`/billing/packs/${id(packId)}/checkout`, { method: "POST" })).url,
631
- checkoutPlan: async (planId) => (await json2(`/billing/plans/${id(planId)}/checkout`, { method: "POST" })).url,
618
+ /** A pack's checkout, in `currency` when one is named (R4.5). */
619
+ checkoutPack: async (packId, currency) => (await json2(`/billing/packs/${id(packId)}/checkout`, { method: "POST", ...currency === void 0 ? {} : { body: { currency } } })).url,
620
+ /** A plan's checkout, in `currency` when one is named (R4.5). */
621
+ checkoutPlan: async (planId, currency) => (await json2(`/billing/plans/${id(planId)}/checkout`, { method: "POST", ...currency === void 0 ? {} : { body: { currency } } })).url,
632
622
  portal: async () => (await json2("/billing/portal", { method: "POST" })).url
633
623
  };
634
624
  }
@@ -670,7 +660,7 @@ async function paying(platform, run) {
670
660
  } catch (error) {
671
661
  if (error instanceof TooFewCredits) {
672
662
  throw new CliError(
673
- `this costs ${error.cost} credits and the account has ${error.credits}: nothing was started. Buy credits at ${creditsPage(platform.apiUrl)}`
663
+ `the account's usage limit is reached: this needs more than is left of it, and nothing was started. See the packs and the plans at ${creditsPage(platform.apiUrl)}`
674
664
  );
675
665
  }
676
666
  throw error;
@@ -3726,9 +3716,6 @@ function jobWords(job) {
3726
3716
  const stage = jobStage(job);
3727
3717
  return stage === "failed" && job.reason !== null ? `failed: ${job.reason}` : stage;
3728
3718
  }
3729
- function costOf(prices, request) {
3730
- return costIn(prices, request.kind);
3731
- }
3732
3719
  async function startGeneration(api, project, request) {
3733
3720
  const ids = {};
3734
3721
  for (const [field, images] of Object.entries(request.images ?? {})) {
@@ -3870,6 +3857,29 @@ function sketchSendProblem(sketch, description) {
3870
3857
  function sketchBody(sketch, description) {
3871
3858
  return { ...sentSketch(sketch, description) };
3872
3859
  }
3860
+ function usageOf$1(billing) {
3861
+ return billing.usage ?? { used: 0, limit: billing.credits };
3862
+ }
3863
+ function usedPercent(usage) {
3864
+ if (usage.limit <= 0) return 100;
3865
+ return Math.min(100, Math.max(0, Math.round(usage.used / usage.limit * 100)));
3866
+ }
3867
+ function shareWords(credits, limit) {
3868
+ if (limit <= 0) return "more than is left";
3869
+ const share = credits / limit * 100;
3870
+ if (share > 0 && share < 1) return "less than 1%";
3871
+ return `about ${Math.round(share)}%`;
3872
+ }
3873
+ function leftWords(usage) {
3874
+ return `${100 - usedPercent(usage)}% of your limit left`;
3875
+ }
3876
+ function costShareWords(prices, kind, usage) {
3877
+ const price = prices.kinds[kind];
3878
+ if (price === void 0) return null;
3879
+ const most = "credits" in price ? price.credits : price.upTo;
3880
+ const share = `${shareWords(most, usage.limit)} of your limit`;
3881
+ return "credits" in price ? share : `up to ${share}, what it does not use given back`;
3882
+ }
3873
3883
  const LARGEST_FOOTPRINT = {
3874
3884
  cols: Math.min(Math.floor(WINDOW_PX.width / SCALES.at(-1)), Math.floor(MAX_ASSEMBLED_PX / TILE_SIZE)),
3875
3885
  rows: Math.min(Math.floor(WINDOW_PX.height / SCALES.at(-1)), Math.floor(MAX_ASSEMBLED_PX / TILE_SIZE))
@@ -4070,15 +4080,17 @@ async function requestOf(context, asked, args) {
4070
4080
  } else if (stringOf(args, "tiles") !== void 0) throw new CliError("--tiles is a level design's");
4071
4081
  return { kind, body, ...references.length > 0 ? { images: { references } } : {}, words };
4072
4082
  }
4073
- async function confirmed(context, platform, what, cost, yes) {
4074
- const credits = (await platform.web.billing()).credits;
4075
- const priced = cost === null ? "at a cost the platform will say" : `for ${cost}`;
4076
- context.io.warn(`Making ${what}, ${priced}; the account has ${credits}.
4083
+ async function confirmed(context, platform, what, prices, kind, yes) {
4084
+ const usage = usageOf$1(await platform.web.billing());
4085
+ const share = costShareWords(prices, kind, usage);
4086
+ const priced = share === null ? "using what the platform will say of your limit" : `using ${share}`;
4087
+ context.io.warn(`Making ${what}, ${priced}; ${leftWords(usage)}.
4077
4088
  `);
4078
- if (yes) return;
4089
+ if (yes) return share;
4079
4090
  if (!context.io.interactive) throw new CliError("nothing was started: pass --yes to start it without a question");
4080
4091
  const said = (await context.io.ask("Start it? [y/N] ")).trim().toLowerCase();
4081
4092
  if (said !== "y" && said !== "yes") throw new CliError("nothing was started");
4093
+ return share;
4082
4094
  }
4083
4095
  async function followed(context, platform, jobId) {
4084
4096
  const since = context.services.now();
@@ -4118,7 +4130,7 @@ const generate = {
4118
4130
  },
4119
4131
  reference: { type: "string", multiple: true, value: "<png>", description: "an image to draw from" },
4120
4132
  quality: { type: "string", value: "<quality>", description: "the platform's to choose now; ignored, and said so" },
4121
- yes: { type: "boolean", short: "y", description: "start it without asking, once the cost is said" },
4133
+ yes: { type: "boolean", short: "y", description: "start it without asking, once what it uses of your limit is said" },
4122
4134
  as: { type: "string", value: "<name>", description: "land it under this name instead of the platform's" },
4123
4135
  aside: { type: "boolean", description: "keep a folder's own map and art under pixelvibing/before-platform/, and write it from the platform" }
4124
4136
  },
@@ -4131,15 +4143,14 @@ const generate = {
4131
4143
  await checkFolder(local, flagOf(args, "aside"));
4132
4144
  const platform = await signedIn(context);
4133
4145
  const prices = await platform.web.prices();
4134
- const cost = costOf(prices, request);
4135
- await confirmed(context, platform, request.words, costWords(prices, request.kind), flagOf(args, "yes"));
4146
+ const uses = await confirmed(context, platform, request.words, prices, request.kind, flagOf(args, "yes"));
4136
4147
  const jobId = await paying(platform, () => startGeneration(platform.web, project, request));
4137
4148
  context.io.warn(`Started the job ${jobId}.
4138
4149
  `);
4139
4150
  const job = await followed(context, platform, jobId);
4140
4151
  if (job.state === "failed") throw new CliError(`the generation failed: ${job.reason ?? "the platform gave no reason"}`);
4141
4152
  const pulled = await pullAsset(platform, local, jobId, stringOf(args, "as"), { aside: flagOf(args, "aside") });
4142
- answer(context, { job: jobId, cost, ...pulled }, pulledWords(pulled));
4153
+ answer(context, { job: jobId, uses, ...pulled }, pulledWords(pulled));
4143
4154
  }
4144
4155
  };
4145
4156
  const mcpHeaders = {
@@ -4291,7 +4302,7 @@ const guideRevise = {
4291
4302
  summary: "Ask the platform to revise the style guide by an instruction, then pull it",
4292
4303
  usage: "<instruction> [--yes] [--force]",
4293
4304
  options: {
4294
- yes: { type: "boolean", short: "y", description: "start it without asking, once the cost is said" },
4305
+ yes: { type: "boolean", short: "y", description: "start it without asking, once what it uses of your limit is said" },
4295
4306
  force: { type: "boolean", description: "replace changes to the local copy that were never pushed" }
4296
4307
  },
4297
4308
  positionals: [1, 1],
@@ -4302,8 +4313,7 @@ const guideRevise = {
4302
4313
  if (instruction === "") throw new CliError("say what to change in the style guide");
4303
4314
  if (!flagOf(args, "force") && await unpushed(local)) throw new CliError(CHANGED);
4304
4315
  const platform = await signedIn(context);
4305
- const cost = costWords(await platform.web.prices(), GUIDE_KIND);
4306
- await confirmed(context, platform, `a revision of the style guide: ${instruction}`, cost, flagOf(args, "yes"));
4316
+ await confirmed(context, platform, `a revision of the style guide: ${instruction}`, await platform.web.prices(), GUIDE_KIND, flagOf(args, "yes"));
4307
4317
  const jobId = await paying(platform, () => platform.web.reviseGuide(project, instruction));
4308
4318
  context.io.warn(`Started the job ${jobId}.
4309
4319
  `);
@@ -4595,7 +4605,7 @@ const move = {
4595
4605
  );
4596
4606
  }
4597
4607
  };
4598
- const version = "0.38.2";
4608
+ const version = "0.39.0";
4599
4609
  const manifest = {
4600
4610
  version
4601
4611
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelvibing/cli",
3
- "version": "0.38.2",
3
+ "version": "0.39.0",
4
4
  "description": "PixelVibing: pixel art game assets from the command line, and the Skills that teach coding assistants to make them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://pixelvibing.com",
@@ -36,17 +36,17 @@ pixel-vibing-cli generate icons --names "<name>,<name>,..." --purpose <skill|ite
36
36
  pixel-vibing-cli generate ui --sketch <file.json> --instruction "<the set, in a line>" --yes --json
37
37
  ```
38
38
 
39
- Each generation spends the account's credits.
40
- - **The cost.** The command prints the cost and the balance before it starts. Without
41
- `--yes` it asks, and with nobody at the terminal it starts nothing. Tell the person what
42
- a batch will cost before you start one.
39
+ Each generation uses part of the account's usage limit for the month.
40
+ - **What it uses.** The command prints the most it uses and what is left, as shares of the
41
+ limit, before it starts. Without `--yes` it asks, and with nobody at the terminal it starts
42
+ nothing. Tell the person how much of their limit a batch will use before you start one.
43
43
  - **The instruction.** Describe the thing as a person would: its look, its material, what
44
44
  it is for in the game. Describe the thing itself, not a prompt for a model.
45
45
  - **The options.**
46
46
  - `--size` is an object's footprint in whole tiles, `1x1` unless given.
47
47
  - `--tiles` is how many tiles a level design is across, `6x4` unless given.
48
- - No quality is given: the platform draws each asset at the quality it needs. The cost
49
- printed is the most it may take, and what it does not use is given back.
48
+ - No quality is given: the platform draws each asset at the quality it needs. The share
49
+ printed is the most it may use, and what it does not use is given back.
50
50
  - `--reference` is a PNG to draw from, repeated for several: at most 8 for an object,
51
51
  3 for a creature, a level design or a UI, none for icons. A UI's references are a style
52
52
  to follow (frames, materials, a way of drawing), never what to draw.
@@ -68,8 +68,8 @@ Each generation spends the account's credits.
68
68
  counts and values in the game.
69
69
  - **Success.** The asset is pulled at once. The answer's `files` are where its pictures
70
70
  are, relative to `pixelvibing/`.
71
- - **Failure.** Too few credits starts nothing and names the page where the person buys
72
- more: tell them, and do not retry. A failed job prints its reason.
71
+ - **Failure.** A reached limit starts nothing and names the page where the person adds to
72
+ it with a pack or a plan: tell them, and do not retry. A failed job prints its reason.
73
73
 
74
74
  ## Listing and pulling
75
75
 
@@ -31,7 +31,7 @@ Do not change it for one asset: say that in the asset's instruction instead.
31
31
  changes, bring it up to date too. The whole guide is at most 8000 characters.
32
32
  - **Ask the platform.** `pixel-vibing-cli guide revise "<what changed in the game>"
33
33
  --yes` revises the guide by that instruction, keeping what it does not change, and
34
- pulls the result. It costs a credit.
34
+ pulls the result. It uses a sliver of the account's limit.
35
35
  3. `pixel-vibing-cli guide push` keeps the file as the guide's next version.
36
36
  - If the platform's guide moved on since your pull, nothing is kept.
37
37
  - Save your changes aside, run `pixel-vibing-cli guide pull --force`, make them again on