Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ func run(t *testing.T, args ...string) (string, error) {
return out.String(), err
}

func TestRootHelpShowsArtWithoutPollutingCommandHelp(t *testing.T) {
rootHelp, err := run(t, "--help")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(rootHelp, " vapour\n") || !strings.Contains(rootHelp, " . : .") || !strings.Contains(rootHelp, "Docs: "+DocsURL) {
t.Fatalf("root help missing art or docs: %q", rootHelp)
}
if strings.Contains(rootHelp, "\x1b[") {
t.Fatalf("root help adds terminal colors to redirected output: %q", rootHelp)
}
commandHelp, err := run(t, "scan", "--help")
if err != nil {
t.Fatal(err)
}
if strings.Contains(commandHelp, " vapour\n") {
t.Fatalf("command help contains root art: %q", commandHelp)
}
}

func pngFile(t *testing.T, path string, w, h int) {
t.Helper()
var b bytes.Buffer
Expand Down
33 changes: 29 additions & 4 deletions cmd/llm_fallback.txt
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
# Vapour developer guide for LLMs
Version: 2026-10-16
Version: 2026-10-17
Source: https://vapour-dev-docs.pages.dev/llm.txt

Status terms in this file:
- Released: on vapour-cli main.
- Preview: merged in source but not guaranteed in a published release yet.
- Released: available in a published CLI release; check your installed binary's help for its commands.
- Preview: merged in source but not guaranteed in a published release or deployed service yet.

1) What Vapour is and the normal flow

Vapour is a browser-game publishing flow around one per-game manifest (`vapour.toml`), a store page bundle (`store/page.json` + `store/template.md`), and a web export folder (or prebuilt zip).

Install the CLI (macOS/Linux): `curl -fsSL https://raw.githubusercontent.com/vapour-computer/vapour-cli/main/install.sh | sh`. Or `go install github.com/vapour-computer/vapour-cli@latest` (Go 1.26+): that installs an executable named `vapour-cli`, so symlink or rename it to `vapour`. Windows: download the archive from https://github.com/vapour-computer/vapour-cli/releases. Update with `vapour update`. Do not tell users to clone and build the CLI.

Typical flow:
1. `vapour login --token vpr_...`
2. `vapour create "My Game" --engine godot` (or `vapour template <engine> "My Game"`)
3. Export/build to `build/web/` so `index.html` exists there
4. `vapour test`
5. `vapour publish --version 0.1.0 --notes "First release"`
6. `vapour verify --remote --version 0.1.0 build.zip` (preview, CLI v0.2) to see the server zip-check result

Rules that often matter:
- `vapour publish` requires `--version` and exactly one of `--notes` or `--notes-file`, including dry runs.
- Each game version can be published once; publishing the same version again returns a version-exists error.
- `vapour publish --dry-run` validates and packs locally, without API auth/upload.
- ZIP publishing scans and signs the exact packed build before upload, including dry runs. A failing scan stops before a version is used.
- Upload is not the same as going live: the server verifies the ZIP, then a Vapour admin reviews the version.
- Store push and publish can register a project and write `project.id` into `vapour.toml` on first server registration.
- A new version is not live until an admin approves it (preview; while it waits, players keep the previous approved version).

Getting this file:
- CLI: `vapour llm`
- Options: `--refresh`, `--offline`, `--url <URL>`
- Env override: `VAPOUR_LLM_URL`
- Direct URL: `https://vapour-dev-docs.pages.dev/llm.txt`
- Guided tutorial for every top-level CLI command: `https://vapour-dev-docs.pages.dev/tutorials/cli.html`

2) `vapour.toml` reference (fields assistants should use)

Expand Down Expand Up @@ -174,13 +181,31 @@ CLI publishing contract:

Current limits (source values):
- Build ZIP upload: 512 MiB
- Extracted archive: 1 GiB
- One file unpacked: 512 MiB
- Extracted archive (total): 1 GiB
- ZIP entries: 5,000
- Store cover image: 2 MiB
- Store page Markdown: 20,000 characters
- Store tagline: 140 characters
- Tags: max 5, each 1–24 characters

Zip scan and signing (preview, CLI v0.2):
- `vapour scan <zip|dir>` (also `--dir`, `--json`) checks a build locally; exit 1 on any error finding. It writes no attestation.
- `vapour verify <zip> [--attestation <file>] [--json]` checks a zip against its attestation locally.
- `vapour publish` (including `--dry-run`) runs the scan, signs the zip and writes `.vapour/build.zip.attestation.json`. There is no flag to skip the scan. With `--experimental-chunks` the scan runs but no attestation is sent.
- `vapour verify <zip> --remote --version <semver>` also compares the server's recorded SHA-256 and status; exit 1 unless the status is `verified`.
- `vapour verify --remote --version <semver>` may omit the ZIP: it checks the signed attestation and server record, but not local ZIP bytes. `--dir` selects the project and `--json` emits a result object.
- Server verification is asynchronous: the upload is accepted right away and the server records `pending`, then `verified`, `rejected` or `unverified`. `unverified` means no attestation was sent (older CLI) and the server scan passed.
- Reject reasons include `bad_signature`, `hash_mismatch`, `size_mismatch`, `bad_key_id`, `bad_key`, `unsupported_version`, `unknown_key`, `bad_attestation`, or any scan code. A rejected version is taken offline and can never be approved; publish a new version.
- Malformed requests (invalid attestation JSON, attestation over 64 KiB) fail immediately with HTTP 400 `invalid_upload`.
- The first valid signing key a developer account uses is remembered; a zip signed by a different key is rejected with `unknown_key`. Losing the key requires a platform-team reset.

Review and approval (preview, not yet deployed):
- Every version needs an admin approval before it is playable, including new versions of already live games.
- Flow: publish → zip check (`pending` → `verified` or `rejected`) → admin review (`pending` → `approved` or `rejected`) → live.
- While a new version waits, players keep playing the previous approved version; a first version stays hidden.
- Admins cannot approve a version whose zip check is `pending` or `rejected`, and must explicitly accept `unverified` versions, so always send the attestation.

Archive safety checks include rejecting path traversal/unsafe paths. Directory packing also rejects common native binaries and symlinks. Prebuilt zips still get server validation.

Compression note:
Expand Down
15 changes: 15 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ func newRoot() *cobra.Command {
SilenceErrors: true,
Version: Version,
}
defaultHelp := root.HelpFunc()
root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if cmd == root {
out := cmd.OutOrStdout()
blue := ""
reset := ""
if file, ok := out.(*os.File); ok {
if info, err := file.Stat(); err == nil && info.Mode()&os.ModeCharDevice != 0 {
blue, reset = "\x1b[34m", "\x1b[0m"
}
}
fmt.Fprintf(out, " %s. : .\n ' .%s\n vapo%sur%s\n\n", blue, reset, blue, reset)
}
defaultHelp(cmd, args)
})
root.PersistentFlags().StringVar(&apiFlag, "api", "", "API base URL (default: saved login, or "+config.DefaultAPI+")")
root.PersistentFlags().BoolVar(&allowCustomAPI, "allow-custom-api", false, "allow non-default API hosts (unsafe: tokens are sent to that host)")
root.PersistentFlags().BoolVar(&allowInsecureHTTP, "allow-insecure-http", false, "allow plain HTTP only for localhost")
Expand Down
18 changes: 14 additions & 4 deletions docs/llm/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@
Flags: `--dir`, required `--version`, exactly one of `--notes` or `--notes-file`, `--dry-run`, `--skip-store`,
`--no-perf-hints`, `--experimental-chunks`.

Order: load + validate manifest → load store bundle (unless `--skip-store`) → resolve release notes
(`--notes-file` or `--notes`, non-empty after trimming; ≤5000 chars) → pack the build → (stop here on `--dry-run`) → register
project if no id → push store page → upload build. Store goes first so rejected media doesn't use up a version.
Order for default ZIP publishing: load + validate manifest → load store bundle (unless `--skip-store`) → resolve release notes
(`--notes-file` or `--notes`, non-empty after trimming; ≤5000 chars) → pack the build → scan the ZIP → sign it and write
`.vapour/build.zip.attestation.json` → (stop here on `--dry-run`) → register project if no id → push store page → upload
build and attestation. Store goes first so rejected media doesn't use up a version. The server verifies the ZIP, then an
admin reviews the version before it goes live.

Build packing (`zipdir`): a folder must contain top-level `index.html` and no native binaries
(`.exe .dll .so .dylib .app .msi`); it is zipped (skips `.git`, `.DS_Store`, `Thumbs.db`) to a temp file. A `.zip` is
uploaded as is. `build.dir` is resolved through symlinks and must stay inside the game folder. Limit 512 MiB.
Uploaded as `build.zip`.

`vapour scan [zip-or-folder]` runs the same static checks without publishing. `--dir` selects a game (no argument
uses `build.dir`); `--json` emits findings. Errors stop upload, but passing is not an antivirus guarantee.
`vapour verify [zip]` checks the Ed25519 attestation signature and, when a ZIP is supplied, its SHA-256. By default it
looks for `<zip>.attestation.json` then the game's `.vapour/build.zip.attestation.json`; `--attestation` overrides this.
`--remote` checks the saved project's server verification record and requires login. Without a ZIP, remote verification
does not check local ZIP bytes. `--version` selects the version (default `project.version`); `--json` emits a result.

Unless suppressed by `--no-perf-hints`, command output also includes warnings from `internal/perfcheck`.

A version can be published once: API error `version_exists` becomes "pass a new --version".
Success prints the game URL.

`--experimental-chunks`: opt-in content-addressed chunk protocol (`pkg/delivery/README.md`); the deployed API doesn't
implement it yet. Default remains ZIP. Upload chunk URLs must be HTTPS and on an allowlisted public host.
implement it yet. Default remains ZIP. The chunk path scans but does not send a signed ZIP attestation or release notes.
Upload chunk URLs must be HTTPS and on an allowlisted public host.
Loading