@velven/cli 0.5.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - `--type` and velven.json's `type` and `how_made` are gone: Velven no longer asks what kind of project it is, and the
6
+ page no longer shows a prompt. This version refuses them with a sentence; remove them from velven.json. A new space
7
+ needs only `title` and `devices`. Velven ignores both keys from older versions.
8
+
9
+ ## 0.5.1
10
+
11
+ - `velven create credits` names the free allowance as this week's: Velven's builder now refills it weekly, from Monday
12
+ 00:00 UTC, instead of daily. The refusal and the low-credit line say "this week" for the same reason. Works with the
13
+ Velven of 0.5.0 and later.
14
+
3
15
  ## 0.5.0
4
16
 
5
17
  Needs the Velven of the same release, which speaks only this version: update to 0.5.0. 0.3.0 and 0.4.0 can no longer
package/README.md CHANGED
@@ -20,19 +20,18 @@ and neither is a `velven.json` in any folder below, whatever its letter case.
20
20
  ```json
21
21
  {
22
22
  "title": "Star Hop",
23
- "type": "game",
24
23
  "devices": ["desktop", "mobile"],
25
24
  "description": "Hop between stars before the light runs out.",
26
25
  "engine": "three.js"
27
26
  }
28
27
  ```
29
28
 
30
- Three fields are required: `title`, `type` (`game`, `world`, `tool` or `wonder`) and `devices` (`desktop`, `mobile`,
31
- `vr`). If one is missing and you're in a terminal, `velven publish` asks for it and saves your answer in `velven.json`.
32
- Anywhere else, it stops and lists each missing field.
29
+ Two fields are required: `title` and `devices` (`desktop`, `mobile`, `vr`). If one is missing and you're in a
30
+ terminal, `velven publish` asks for it and saves your answer in `velven.json`. Anywhere else, it stops and lists each
31
+ missing field. Since 0.6.0, a `velven.json` that still has `type` or `how_made` is refused with exit 2
32
+ (`invalid_config`): Velven no longer uses them, so remove them and publish again.
33
33
 
34
- The optional fields are `description`, `engine`, `ai_tools`, `models`, `how_made` and `source_url`, plus these for
35
- hosting:
34
+ The optional fields are `description`, `engine`, `ai_tools`, `models` and `source_url`, plus these for hosting:
36
35
 
37
36
  | Key | What it does |
38
37
  | --- | --- |
@@ -139,8 +138,8 @@ with exit 6 if it has one of these in it.
139
138
  live. A refused version prints the reason. A version Velven couldn't judge prints what Velven saw, and a start hint
140
139
  usually fixes it. Either way, you can ask for a review from the printed link. The exception is a program or a miner,
141
140
  which is never served: remove the file and publish again.
142
- - `--title`, `--type`, `--devices desktop,mobile`, `--description` and `--engine` override `velven.json` for this run
143
- only.
141
+ - `--title`, `--devices desktop,mobile`, `--description` and `--engine` override `velven.json` for this run only.
142
+ `--type` is refused with exit 2: Velven no longer asks what kind of project it is.
144
143
  - `--yes` never asks for missing fields. It fails and lists them instead.
145
144
  - `--json` prints one JSON object, for scripts and agents. It also works on `whoami` and `versions`.
146
145
 
@@ -177,7 +176,7 @@ velven create send "a cozy game where you grow a garden on a floating island"
177
176
  | `velven create show --timeline` | The newest turn's timeline as a waterfall (see below). `--turn <id>` picks another turn, `--all` lists every tool call |
178
177
  | `velven create refs` | The project's reference pictures: each id, when it was added, its size and how many messages carried it. `--project <id>` picks another project |
179
178
  | `velven create refs rm <ref-n>` | Delete a reference picture, sent or not. The messages that carried it keep its number, and the number is never given to another picture |
180
- | `velven create credits` | Your credits: today's allowance, your balance, what open turns hold, and the latest entries |
179
+ | `velven create credits` | Your credits: this week's allowance, your balance, what open turns hold, and the latest entries |
181
180
  | `velven create usage` | What each of your turns held and used, newest first. `--project <id>` keeps one project's, `--before <turn>` reads the page before (the last line names it), `--json` prints the page |
182
181
  | `velven create stats` | For Velven's admins: p50 and p90 over recent turns. `--days <n>` (1 to 90, 7 by default), `--origin <origin>` |
183
182
 
@@ -248,10 +247,10 @@ turn could not take included: "Could not be taken this turn; queued, but the que
248
247
  with the first: nothing is started or held twice, and the CLI prints "(already sent)" before its line.
249
248
 
250
249
  The CLI says what the turn does as it goes. What the agents write goes to standard output, and their progress goes to
251
- standard error. When Velven picked the work, it says so in words first ("I'll make the change."; when the day's
250
+ standard error. When Velven picked the work, it says so in words first ("I'll make the change."; when this week's
252
251
  credits kept it from a fuller turn, "I'll make the change. I kept this one quick to save credits."), with the work it
253
252
  picked and why on standard error. On a build it prints the plain step it is at ("Designing your game", "Painting the art", "Building the world", "Trying it out", "Polishing", "Finishing up"; a later small change says "Making the change" for its work), and on a first build the design as it is settled ("Designing Petal Drift: ..." with its style and palette) and each picture once it lands ("Painted sky (1024 by 512)" with a link to look at it). You see what each step changed, the live link while the game is being built, each playtest's preview
254
- link (the first playable one, before the turn ends), its screenshots and its clip. When the turn ends, it prints the reply (when the turn asked a question, which it already printed with its choices, only what follows it), then, after a turn that ended with a playable game, "Where it could go next:" with two to four short suggestions for this game and the command that sends the first (`velven create send "Add a boss at the end"`; each is an ordinary message), then the preview link and the credits used. A reply may end with one sentence on what the turn took from your pictures, and with "You're running low on credits for today." when what is left would not cover another small change. A turn that stops at
253
+ link (the first playable one, before the turn ends), its screenshots and its clip. When the turn ends, it prints the reply (when the turn asked a question, which it already printed with its choices, only what follows it), then, after a turn that ended with a playable game, "Where it could go next:" with two to four short suggestions for this game and the command that sends the first (`velven create send "Add a boss at the end"`; each is an ordinary message), then the preview link and the credits used. A reply may end with one sentence on what the turn took from your pictures, and with "You're running low on credits for this week." when what is left would not cover another small change. A turn that stops at
255
254
  its credit cap or time limit saves what it finished and ends with exit 0. A failed turn costs nothing. When a new game's
256
255
  first build stops before it made the game (it saved nothing, or only its design), the reply ends "Press Build it to try
257
256
  again." and the CLI prints `Try again: velven create send --build "Build it"`; "try again" typed after it builds too.
package/dist/velven.js CHANGED
@@ -23,7 +23,7 @@ class CliError extends Error {
23
23
  }
24
24
  }
25
25
  // package.json
26
- var version = "0.5.0";
26
+ var version = "0.6.0";
27
27
 
28
28
  // src/version.ts
29
29
  var VERSION = version;
@@ -2490,7 +2490,7 @@ async function credits(ctx, api, json) {
2490
2490
  ctx.out(JSON.stringify({ ok: true, credits: view }));
2491
2491
  return EXIT.ok;
2492
2492
  }
2493
- ctx.out(`${plural(view.available, "credit")} available: ${view.daily_left} left of today's allowance, ${view.balance} in your balance, ${view.held} held by open turns.`);
2493
+ ctx.out(`${plural(view.available, "credit")} available: ${view.daily_left} left of this week's allowance, ${view.balance} in your balance, ${view.held} held by open turns.`);
2494
2494
  for (const row of view.ledger.slice(0, 10)) {
2495
2495
  const amount = row.amount > 0 ? `+${row.amount}` : String(row.amount);
2496
2496
  ctx.out(` ${when(row.created_at).padEnd(20)} ${amount.padStart(6)} ${row.kind}${row.turn_id !== null ? ` (turn ${row.turn_id})` : ""}`);
@@ -3155,7 +3155,6 @@ function mediaProblem(kind, path, size2, bytes, missing = "is not among the file
3155
3155
  // src/project.ts
3156
3156
  import { readdir, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
3157
3157
  import { join as join3, win32 } from "node:path";
3158
- var SPACE_TYPES = ["game", "world", "tool", "wonder"];
3159
3158
  var DEVICES = ["desktop", "mobile", "vr"];
3160
3159
  var PROJECT_FILE = "velven.json";
3161
3160
  var isProjectName = (name) => name.toLowerCase() === PROJECT_FILE;
@@ -3188,6 +3187,15 @@ async function readProject(dir) {
3188
3187
  }
3189
3188
  return { path, exists: true, data };
3190
3189
  }
3190
+ var RETIRED_KEYS = ["type", "how_made"];
3191
+ function refuseRetiredKeys(data) {
3192
+ const found = RETIRED_KEYS.filter((key) => data[key] !== undefined);
3193
+ if (!found.length)
3194
+ return;
3195
+ const named = found.map((key) => `"${key}"`).join(" and ");
3196
+ const sentence = found.length === 1 ? "Remove it and publish again." : "Remove them and publish again.";
3197
+ throw new CliError(`${PROJECT_FILE} has ${named}, which Velven no longer uses. ${sentence}`, EXIT.usage, "invalid_config");
3198
+ }
3191
3199
  async function updateProject(project, values) {
3192
3200
  for (const [key, value] of Object.entries(values)) {
3193
3201
  if (value === undefined)
@@ -3206,8 +3214,6 @@ function withFlags(data, flags) {
3206
3214
  const out = { ...data };
3207
3215
  if (flags.title !== undefined)
3208
3216
  out.title = flags.title;
3209
- if (flags.type !== undefined)
3210
- out.type = flags.type.trim().toLowerCase();
3211
3217
  if (flags.devices !== undefined)
3212
3218
  out.devices = list2(flags.devices);
3213
3219
  if (flags.description !== undefined)
@@ -3216,7 +3222,7 @@ function withFlags(data, flags) {
3216
3222
  out.engine = flags.engine;
3217
3223
  return out;
3218
3224
  }
3219
- var REQUIRED = ["title", "type", "devices"];
3225
+ var REQUIRED = ["title", "devices"];
3220
3226
  function requiredProblem(field, value) {
3221
3227
  if (value === undefined || value === null || value === "" || Array.isArray(value) && value.length === 0)
3222
3228
  return "missing";
@@ -3227,9 +3233,6 @@ function requiredProblem(field, value) {
3227
3233
  return "must be under 80 characters";
3228
3234
  return null;
3229
3235
  }
3230
- if (field === "type") {
3231
- return typeof value === "string" && SPACE_TYPES.includes(value) ? null : `must be one of ${SPACE_TYPES.join(", ")}`;
3232
- }
3233
3236
  if (!Array.isArray(value) || !value.every((d) => typeof d === "string" && DEVICES.includes(d))) {
3234
3237
  return `must be a list from ${DEVICES.join(", ")}, like ["desktop", "mobile"]`;
3235
3238
  }
@@ -3262,7 +3265,6 @@ var OPTIONAL = {
3262
3265
  engine: [isString, 'must be text, like "three.js"'],
3263
3266
  ai_tools: [isStringList, "must be a list of text"],
3264
3267
  models: [isStringList, "must be a list of text"],
3265
- how_made: [(v) => isString(v) && v.length <= 2000, "must be text, under 2000 characters"],
3266
3268
  source_url: [isString, "must be a web address"],
3267
3269
  entry: [isInsidePath, 'must be a path inside the folder, like "index.html"'],
3268
3270
  thumbnail: [isInsidePath, 'must be a path inside the folder, like "media/thumbnail.jpg"'],
@@ -3293,14 +3295,11 @@ function requiredProblems(data) {
3293
3295
  }
3294
3296
  var QUESTIONS = {
3295
3297
  title: "Title, the name people see on Velven: ",
3296
- type: `Type (${SPACE_TYPES.join(", ")}): `,
3297
3298
  devices: `Devices it works on, comma separated (${DEVICES.join(", ")}): `
3298
3299
  };
3299
3300
  function parseAnswer(field, answer) {
3300
3301
  if (field === "title")
3301
3302
  return answer.trim();
3302
- if (field === "type")
3303
- return answer.trim().toLowerCase();
3304
3303
  return list2(answer);
3305
3304
  }
3306
3305
  async function completeRequired(ctx, project, merged, canAsk) {
@@ -3865,8 +3864,8 @@ var DONE = ["live", "refused", "unjudged", "replaced", "preview"];
3865
3864
  var POLL_MS = 3000;
3866
3865
  var WAIT_LIMIT_MS = 20 * 60 * 1000;
3867
3866
  function listingFields(config) {
3868
- const fields = { title: config.title, space_type: config.type, devices: config.devices };
3869
- for (const key of ["description", "engine", "ai_tools", "models", "how_made", "source_url"]) {
3867
+ const fields = { title: config.title, devices: config.devices };
3868
+ for (const key of ["description", "engine", "ai_tools", "models", "source_url"]) {
3870
3869
  if (config[key] !== undefined)
3871
3870
  fields[key] = config[key];
3872
3871
  }
@@ -3925,6 +3924,7 @@ async function publish(ctx, options) {
3925
3924
  const creds = await findToken(ctx, base);
3926
3925
  const guest = !creds;
3927
3926
  const project = await readProject(dir);
3927
+ refuseRetiredKeys(project.data);
3928
3928
  const merged = withFlags(project.data, options);
3929
3929
  const shape = optionalProblems(merged);
3930
3930
  if (shape.length)
@@ -4209,6 +4209,7 @@ async function reset(ctx, options) {
4209
4209
  }
4210
4210
 
4211
4211
  // src/cli.ts
4212
+ var TYPE_FLAG_GONE = "--type is no longer an option: Velven no longer asks what kind of project it is. Run the command without it.";
4212
4213
  var HELP = `velven ${VERSION}: publish a folder to Velven.
4213
4214
 
4214
4215
  Usage:
@@ -4220,7 +4221,6 @@ Usage:
4220
4221
  (not signed in: an unlisted page with a claim token, no --prod)
4221
4222
  --wait Wait for the preview link, or with --prod until it is live
4222
4223
  --title <text> Override velven.json for this run
4223
- --type <type> game, world, tool or wonder
4224
4224
  --devices <list> desktop, mobile, vr (comma separated)
4225
4225
  --description <text>
4226
4226
  --engine <engine>
@@ -4261,18 +4261,21 @@ var COMMANDS = {
4261
4261
  description: "string",
4262
4262
  engine: "string"
4263
4263
  },
4264
- run: (ctx, p) => publish(ctx, {
4265
- dir: p.positionals[0],
4266
- prod: p.flags.prod === true,
4267
- wait: p.flags.wait === true,
4268
- yes: p.flags.yes === true,
4269
- json: p.flags.json === true,
4270
- title: stringFlag(p, "title"),
4271
- type: stringFlag(p, "type"),
4272
- devices: stringFlag(p, "devices"),
4273
- description: stringFlag(p, "description"),
4274
- engine: stringFlag(p, "engine")
4275
- })
4264
+ run: (ctx, p) => {
4265
+ if (p.flags.type !== undefined)
4266
+ throw new CliError(TYPE_FLAG_GONE, EXIT.usage, "usage");
4267
+ return publish(ctx, {
4268
+ dir: p.positionals[0],
4269
+ prod: p.flags.prod === true,
4270
+ wait: p.flags.wait === true,
4271
+ yes: p.flags.yes === true,
4272
+ json: p.flags.json === true,
4273
+ title: stringFlag(p, "title"),
4274
+ devices: stringFlag(p, "devices"),
4275
+ description: stringFlag(p, "description"),
4276
+ engine: stringFlag(p, "engine")
4277
+ });
4278
+ }
4276
4279
  },
4277
4280
  versions: {
4278
4281
  flags: { json: "boolean", space: "string" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velven/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Publish a folder to Velven, the community marketplace for spaces built with AI: a private preview first, then hosted and live on its own page in seconds.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://velven.ai/docs",