From 44948363d047878b1749ca77d2137120efcf6d67 Mon Sep 17 00:00:00 2001 From: OpenCode Agent Date: Sat, 17 Oct 2026 00:25:19 +0530 Subject: [PATCH 1/2] Refresh CLI assistant guide and add help banner --- cmd/cmd_test.go | 17 +++++++++++++++++ cmd/llm_fallback.txt | 16 +++++++++++++--- cmd/root.go | 11 ++++++++++- docs/llm/publish.md | 18 ++++++++++++++---- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 6071902..9b79082 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -112,6 +112,23 @@ 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, "\\ V /") || !strings.Contains(rootHelp, "Docs: "+DocsURL) { + t.Fatalf("root help missing art or docs: %q", rootHelp) + } + commandHelp, err := run(t, "scan", "--help") + if err != nil { + t.Fatal(err) + } + if strings.Contains(commandHelp, "\\ V /") { + 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 diff --git a/cmd/llm_fallback.txt b/cmd/llm_fallback.txt index a70c26e..5e5c75b 100644 --- a/cmd/llm_fallback.txt +++ b/cmd/llm_fallback.txt @@ -1,10 +1,10 @@ # 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. +- Source preview: implemented on vapour-cli main; check the installed binary's help for release availability. +- Planned: not available in the current CLI or deployed service. 1) What Vapour is and the normal flow @@ -21,6 +21,8 @@ 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. Getting this file: @@ -28,6 +30,7 @@ Getting this file: - Options: `--refresh`, `--offline`, `--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) @@ -183,6 +186,13 @@ Current limits (source values): Archive safety checks include rejecting path traversal/unsafe paths. Directory packing also rejects common native binaries and symlinks. Prebuilt zips still get server validation. +ZIP scanning and verification (source-preview CLI): +- `vapour scan [zip-or-folder]` checks zip bombs, unsafe paths, symlinks, native executables, known in-browser miners and the EICAR test string without extracting or running content. With no argument it uses `build.dir`. `--dir` chooses the game folder; `--json` prints findings. This heuristic is not antivirus. +- Default ZIP `vapour publish` scans first and refuses error findings, then signs an Ed25519 attestation for the ZIP's SHA-256 and scan result. Keep the private key in the user's Vapour config directory private. The public attestation is saved at `.vapour/build.zip.attestation.json` inside the game folder. Preserve the exact ZIP if you want to verify its bytes later; folder publishing uses a temporary ZIP. +- `vapour verify ` checks the signature and ZIP hash, using `.attestation.json` if present or `.vapour/build.zip.attestation.json` otherwise. `--attestation ` selects another attestation. +- `vapour verify --remote --version ` checks the signed attestation and the server's verification record; it needs a project ID and login. The ZIP is optional in this mode: without it, no local ZIP bytes are checked. `--dir` selects the project; `--json` emits a result object. The server must mark the version `verified` with the same SHA-256. +- `--experimental-chunks` is a separate preview path: it scans but does not send a signed ZIP attestation; it requires a compatible server. + Compression note: - At publish, eligible assets are stored Brotli-compressed when both conditions hold: extension is `.wasm`, `.js`, `.pck`, `.data`, or `.json`; file size is over 1 KiB; Brotli size is at least 10% smaller. diff --git a/cmd/root.go b/cmd/root.go index 68eefb9..b07410f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,7 +29,16 @@ func newRoot() *cobra.Command { root := &cobra.Command{ Use: "vapour", Short: "Create and publish web games on Vapour", - Long: "Create and publish web games on Vapour.\n\nDocs: " + DocsURL, + Long: `__ __ +\ \ / /_ _ _ __ ___ _ _ _ __ + \ \ / / _' | '_ \ / _ \| | | | '__| + \ V / (_| | |_) | (_) | |_| | | + \_/ \__,_| .__/ \___/ \__,_|_| + |_| + +Create and publish web games on Vapour. + +Docs: ` + DocsURL, PersistentPreRun: func(cmd *cobra.Command, args []string) { maybeCheckForUpdate(cmd) }, diff --git a/docs/llm/publish.md b/docs/llm/publish.md index 3dc253a..73168e2 100644 --- a/docs/llm/publish.md +++ b/docs/llm/publish.md @@ -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 `.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. From 14b41947044898c7a125bfb0e43a080f8bc358f3 Mon Sep 17 00:00:00 2001 From: OpenCode Agent Date: Sat, 17 Oct 2026 00:27:11 +0530 Subject: [PATCH 2/2] Match terminal help logo and sync current handbook guide --- cmd/cmd_test.go | 7 +++++-- cmd/llm_fallback.txt | 35 +++++++++++++++++++++++++---------- cmd/root.go | 26 ++++++++++++++++---------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 9b79082..14b7733 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -117,14 +117,17 @@ func TestRootHelpShowsArtWithoutPollutingCommandHelp(t *testing.T) { if err != nil { t.Fatal(err) } - if !strings.Contains(rootHelp, "\\ V /") || !strings.Contains(rootHelp, "Docs: "+DocsURL) { + 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, "\\ V /") { + if strings.Contains(commandHelp, " vapour\n") { t.Fatalf("command help contains root art: %q", commandHelp) } } diff --git a/cmd/llm_fallback.txt b/cmd/llm_fallback.txt index 5e5c75b..259e2f3 100644 --- a/cmd/llm_fallback.txt +++ b/cmd/llm_fallback.txt @@ -3,19 +3,22 @@ Version: 2026-10-17 Source: https://vapour-dev-docs.pages.dev/llm.txt Status terms in this file: -- Source preview: implemented on vapour-cli main; check the installed binary's help for release availability. -- Planned: not available in the current CLI or deployed service. +- 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 "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. @@ -24,6 +27,7 @@ Rules that often matter: - 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` @@ -177,21 +181,32 @@ 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 -Archive safety checks include rejecting path traversal/unsafe paths. Directory packing also rejects common native binaries and symlinks. Prebuilt zips still get server validation. +Zip scan and signing (preview, CLI v0.2): +- `vapour scan ` (also `--dir`, `--json`) checks a build locally; exit 1 on any error finding. It writes no attestation. +- `vapour verify [--attestation ] [--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 --remote --version ` also compares the server's recorded SHA-256 and status; exit 1 unless the status is `verified`. +- `vapour verify --remote --version ` 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. -ZIP scanning and verification (source-preview CLI): -- `vapour scan [zip-or-folder]` checks zip bombs, unsafe paths, symlinks, native executables, known in-browser miners and the EICAR test string without extracting or running content. With no argument it uses `build.dir`. `--dir` chooses the game folder; `--json` prints findings. This heuristic is not antivirus. -- Default ZIP `vapour publish` scans first and refuses error findings, then signs an Ed25519 attestation for the ZIP's SHA-256 and scan result. Keep the private key in the user's Vapour config directory private. The public attestation is saved at `.vapour/build.zip.attestation.json` inside the game folder. Preserve the exact ZIP if you want to verify its bytes later; folder publishing uses a temporary ZIP. -- `vapour verify ` checks the signature and ZIP hash, using `.attestation.json` if present or `.vapour/build.zip.attestation.json` otherwise. `--attestation ` selects another attestation. -- `vapour verify --remote --version ` checks the signed attestation and the server's verification record; it needs a project ID and login. The ZIP is optional in this mode: without it, no local ZIP bytes are checked. `--dir` selects the project; `--json` emits a result object. The server must mark the version `verified` with the same SHA-256. -- `--experimental-chunks` is a separate preview path: it scans but does not send a signed ZIP attestation; it requires a compatible server. +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: - At publish, eligible assets are stored Brotli-compressed when both conditions hold: diff --git a/cmd/root.go b/cmd/root.go index b07410f..e1b14aa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,16 +29,7 @@ func newRoot() *cobra.Command { root := &cobra.Command{ Use: "vapour", Short: "Create and publish web games on Vapour", - Long: `__ __ -\ \ / /_ _ _ __ ___ _ _ _ __ - \ \ / / _' | '_ \ / _ \| | | | '__| - \ V / (_| | |_) | (_) | |_| | | - \_/ \__,_| .__/ \___/ \__,_|_| - |_| - -Create and publish web games on Vapour. - -Docs: ` + DocsURL, + Long: "Create and publish web games on Vapour.\n\nDocs: " + DocsURL, PersistentPreRun: func(cmd *cobra.Command, args []string) { maybeCheckForUpdate(cmd) }, @@ -46,6 +37,21 @@ Docs: ` + DocsURL, 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")