# Velven CLI

The Velven CLI publishes a folder to Velven, lists its versions, rolls back, and serves it locally inside Velven's page. It is the npm package `@velven/cli`, with no dependencies, for Node 20 or later.

## Install

Run it with `npx`, or install it once and run `velven`:

```bash
npx @velven/cli publish ./dist
# or
npm install -g @velven/cli
velven publish ./dist
```

`velven help`, or `--help` after any command, lists the commands and flags, and `velven --version` prints the release. `velven-cli` is the same tool under a second name.

## login, logout, whoami

```bash
velven login     # shows a code and opens velven.ai to approve it
velven whoami    # @handle on https://velven.ai
velven logout    # forgets this computer's sign-in
```

`login` is the same device-code sign-in [an agent uses](https://velven.ai/docs/agent#login): approve the code in your browser and the token is saved in `~/.config/velven/auth.json`, readable only by you, one per Velven address. It lasts 90 days. `logout` forgets it on this computer; revoke it for good in your [settings](https://velven.ai/settings). `whoami --json` prints `{ ok, api, handle, profileUrl, tokenFrom }`.

## publish

```bash
velven publish [dir]             # a private preview of the folder (default: the current one)
velven publish [dir] --prod      # live once Velven's safety check passes
velven publish [dir] --prod --wait
```

The listing comes from [velven.json](https://velven.ai/docs/hosting#velven-json) in the folder. The first publish creates the space and writes `"space"` into `velven.json`; later publishes update it. When it cannot write the file (read-only, or a folder you cannot write to), it says so, prints what to put in `velven.json` (the slug, or without an account the claim token) and goes on with the publish; `--json` carries the sentence as `notSaved`. Only files Velven does not already have are uploaded.

- `--prod`: Send the version to the [safety check](https://velven.ai/docs/hosting#check); it goes live when the check passes. Without it, the version is a [preview](https://velven.ai/docs/hosting#previews) for 24 hours.
- `--wait`: Wait here for the preview link, or with `--prod` for the check's verdict, up to 20 minutes; it stops early when the check ended without a verdict (the space was hidden, held or moved, or its checks are over the hourly limit), and a publish checks it again. Without it, the CLI returns after the upload with a link to follow.
- `--title <text>`: Override `velven.json` for this run. Nothing is written back.
- `--type <type>`: `game`, `world`, `tool` or `wonder`.
- `--devices <list>`: `desktop`, `mobile`, `vr`, comma separated.
- `--description <text>`: One line, up to 160 characters.
- `--engine <engine>`: An [engine value](https://velven.ai/docs/agent#values), such as `three.js`.
- `--yes, -y`: Never ask. A missing field fails with exit 2 and names every missing field.
- `--json`: Print one JSON object, for scripts and agents.

In a terminal, a missing required field is asked for and your answer is saved in `velven.json`. Anywhere else, or with `--yes` or `--json`, the command fails and names the fields.

Signed out, `publish` makes an [unlisted page](https://velven.ai/docs/hosting#no-account): it prints the page, a claim token and a claim link, and saves the token in `velven.json` as `"claim"`. `--prod` then exits 3. After `velven login`, the next publish from the folder claims the page.

## versions and rollback

```bash
velven versions            # the space's versions; * marks the live one
velven versions --json     # { ok, space, versions: [{ id, number, status, createdAt, live, files, size, reason }] }
velven rollback 3          # version 3 goes live again
velven rollback            # in a terminal: lists the versions and asks which
```

A version's status is `uploading`, `preview`, `checking`, `live`, `refused`, `unjudged` (the check could not judge it), or `replaced` (a newer version took its place, or the space moved to another address before its check finished). A rollback goes only to a version that was live before.

`versions`, `rollback` and `reset` act on the space `velven.json` in the current folder names; `--space <slug>` picks another of yours.

## dev

```bash
velven dev [dir]            # serve the folder and play it on Velven
velven dev [dir] --port 5173
```

`dev` serves the folder on your machine, with no caching and the SDK tag added to the entry page as hosting adds it, and honours `entry` and `spa`. It opens `https://velven.ai/dev`, a Velven page that frames your local server with the player in sandbox mode. Signed in on Velven as the space's owner, sign-in, boards, saves, achievements, stats, content and rooms all work on the real API, and everything they write goes to the space's [sandbox](https://velven.ai/docs/sdk/local#sandbox).

- The sandbox belongs to a space, so publish once first: `dev` reads the space from `velven.json`. Before that it serves the page, and SDK calls answer `unavailable`. To test with a friend, send them a [preview](https://velven.ai/docs/hosting#previews) instead.
- Edit a file and reload the page to see it; there is no build step or watcher of its own. Point it at your build's output folder, or run your own dev server and open the `/dev` page with its address.
- It serves what `publish` sends, by the same rule: nothing `.velvenignore` names (a file under an ignored folder included), and dotfiles, `node_modules` and `velven.json` never, in any case, nor a name that ends in a dot or a space or holds `:` or `~`, nor a link to anything outside the folder. A path is served only in the letter case the folder spells it, as the live version looks paths up: `Assets/Hero.png` does not open `assets/hero.png`, even on a disk that ignores case.
- Press Ctrl+C to stop.

> Note: For work with no network at all, the SDK's [local environment](https://velven.ai/docs/sdk/local) fakes players and boards on the page itself.

## reset

```bash
velven reset                         # the whole sandbox, after a confirmation
velven reset --scores --saves        # only these kinds
velven reset --player mara --yes     # one player's sandbox data, without asking
```

`reset` deletes the test data previews and `velven dev` wrote: `--scores`, `--saves`, `--achievements`, `--content` and `--rooms`, all of them by default. Live players' data is never touched. Sandbox data also clears itself 30 days after its last write.

## Environment variables

- `VELVEN_TOKEN`: A token to use instead of the saved sign-in, for CI. It takes precedence over `velven login`.
- `VELVEN_API` (A web address, default `https://velven.ai`): The Velven to talk to, when you run one of your own for development.

## JSON output

With `--json`, `publish`, `versions` and `whoami` print one JSON object and nothing else, and every failure prints `{ ok: false, error, code, fields? }`: `error` a sentence to show a person, `code` a stable name to branch on.

```json
{
  "ok": true,
  "space": { "slug": "star-hop", "url": "https://velven.ai/s/star-hop" },
  "version": { "id": "…", "number": 4 },
  "status": "live",
  "watchUrl": "https://velven.ai/s/star-hop/edit/files?version=…",
  "pageUrl": "https://velven.ai/mara/star-hop",
  "uploaded": 2,
  "reused": 37
}
```

`status` is `preview` with a `previewUrl`, `checking` without `--wait`, or the verdict with it, with `reason` and `shownOnTake` for a version refused or not judged. Signed out, it adds `pageUrl`, `claimToken`, `claimUrl` and `expiresAt`.

| Code | Meaning |
| --- | --- |
| `missing_fields` | `velven.json` lacks a required field; `fields` names them. |
| `invalid_config` | `velven.json` is not valid JSON, or a field has the wrong shape. |
| `no_entry` | The folder has no `index.html`, or no file at `entry`. |
| `not_owner` | `space` in `velven.json` is someone else's space. |
| `too_large`, `too_many_files` | The version is over the limits; the largest files are named. |
| `sign_in_required` | `--prod` without an account. |
| `claim_spent` | Exit 3: signed out, with `"claim"` in `velven.json` for a page that was claimed (in the browser, say). Run `velven login` as the account that claimed it and publish again. Claimed while `velven publish` uploads: "The page was claimed before this version was finished. Run velven login and publish again." Claimed while `--wait` waits: "The page was claimed. Follow the check on its Files tab", with the link. |
| `rate_limited` | Too many publishes; the sentence says when to try again. |
| `not_signed_in` | A command that needs an account, signed out. |

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Done. |
| `1` | Failed: the network, a server error, a failed upload, or a cancelled prompt. |
| `2` | A mistake in the command or in `velven.json`, such as an unknown flag or a missing field. |
| `3` | Not signed in, or the token was refused, for something that needs an account, such as `--prod`. |
| `4` | Refused by Velven: not your space, too large, too many files, not found, invalid fields. |
| `5` | Rate limited. Try again later. |
| `6` | With `--wait`: the safety check refused the version or could not judge it. |

## Publish from CI

Sign in once with `velven login`, copy the token from `~/.config/velven/auth.json` into your CI's secrets as `VELVEN_TOKEN`, and make sure `velven.json` with `"space"` is committed. Then publish with no prompts and wait for the verdict, so the job fails when the check refuses the version:

```yaml
# .github/workflows/publish.yml
on:
  push:
    branches: [main]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci && npm run build
      - run: npx @velven/cli publish ./dist --prod --yes --wait
        env:
          VELVEN_TOKEN: ${{ secrets.VELVEN_TOKEN }}
```

> Note: Revoke a token you no longer use in your [settings](https://velven.ai/settings). A refused token exits 3.
