diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..42f2f00 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,147 @@ +# metin-release — CLI reference + +Phase 1 commands. All subcommands share the top-level flags. + +## Top-level flags + +| Flag | Description | +|---|---| +| `--version` | Print package version and exit. | +| `--json` | Emit only the JSON envelope on stdout (stderr may still carry logs). | +| `-v`, `--verbose` | Verbose stderr logging. | +| `-q`, `--quiet` | Suppress stderr logging entirely. | + +## Output envelope + +Every command writes a JSON envelope on stdout: + +```json +{ + "ok": true, + "command": "release inspect", + "status": "inspected", + "stats": { "...": "..." } +} +``` + +On failure: + +```json +{ + "ok": false, + "command": "release inspect", + "status": "failed", + "error": { "code": "source_not_found", "message": "..." } +} +``` + +## Exit codes + +| Code | Meaning | +|---|---| +| 0 | Success | +| 1 | Operator or validation error | +| 2 | Remote / network error | +| 3 | Signing or integrity error | +| 4 | Reserved (ERP sync, Phase 2+) | + +## `release inspect` + +Scan a client source root and report file counts plus launcher/main-exe detection. + +``` +metin-release release inspect --source /path/to/client +``` + +No writes. JSON stats: `source_path`, `file_count`, `total_bytes`, `launcher_present`, `main_exe_present`. + +## `release build-manifest` + +Wraps `make-manifest.py` to produce `manifest.json` for a source tree. + +``` +metin-release release build-manifest \ + --source /path/to/client \ + --version 2026.04.14-1 \ + --out /tmp/release/manifest.json \ + [--previous 2026.04.13-3] \ + [--notes release-notes.md] \ + [--launcher Metin2Launcher.exe] \ + [--created-at 2026-04-14T12:00:00Z] +``` + +Override the wrapped script path via the `METIN_RELEASE_MAKE_MANIFEST` env var. + +## `release sign` + +Wraps `sign-manifest.py`. Requires an absolute path to a chmod-600 raw 32-byte Ed25519 key. + +``` +metin-release release sign \ + --manifest /tmp/release/manifest.json \ + --key /home/you/.config/metin/launcher-signing-key \ + [--out /tmp/release/manifest.json.sig] +``` + +Override the wrapped script path via `METIN_RELEASE_SIGN_MANIFEST`. + +## `release diff-remote` + +HEADs every unique blob hash from a manifest against `/files//`. + +``` +metin-release release diff-remote \ + --manifest /tmp/release/manifest.json \ + --base-url https://updates.example.com +``` + +## `release upload-blobs` + +rsyncs the release directory (excluding `manifest.json` and `.sig`) to a target. + +``` +metin-release release upload-blobs \ + --release-dir /tmp/release \ + --rsync-target user@host:/var/www/updates/ \ + [--dry-run] [--yes] +``` + +## `release promote` + +Pushes only `manifest.json` + `manifest.json.sig` to the target top-level — makes the new release live. + +``` +metin-release release promote \ + --release-dir /tmp/release \ + --rsync-target user@host:/var/www/updates/ \ + [--dry-run] [--yes] +``` + +## `release verify-public` + +GETs `manifest.json` + `manifest.json.sig` from a public URL and verifies the Ed25519 signature. Optionally spot-checks random blobs with `--sample-blobs N`. + +``` +metin-release release verify-public \ + --base-url https://updates.example.com \ + --public-key \ + [--sample-blobs 5] +``` + +## `release publish` + +Composite: build-manifest → sign → stage blob tree → upload-blobs → promote → verify-public. Short-circuits on the first failure; the JSON envelope includes a `stages` array with `{name, status, duration_ms, error?}` per step. + +``` +metin-release release publish \ + --source /path/to/client \ + --version 2026.04.14-1 \ + --out /tmp/release \ + --key /home/you/.config/metin/launcher-signing-key \ + --rsync-target user@host:/var/www/updates/ \ + --base-url https://updates.example.com \ + --public-key \ + [--previous ...] [--notes ...] [--launcher ...] \ + [--created-at ...] [--sample-blobs N] \ + [--yes] [--force] [--dry-run-upload] +```