
Govilo To Go
- 1 installs
- 1 repo stars
- Updated February 13, 2026
- hau823823/govilo-to-go
This is a copy of gen-paylink-govilo by hau823823 - installs and ranking accrue to the original listing.
Uploads ZIPs, folders, or files to Govilo R2 storage and generates a paid unlock link priced in USDC via the Govilo Bot API.
About
Packages input files, uploads them to Govilo storage with presigned URLs, and creates a purchasable unlock link through the Govilo Bot API. A developer uses it to sell digital products by generating a Govilo unlock link priced in USDC.
- Presign, upload, then create-item flow via CLI
- Requires GOVILO_API_KEY; input accepts ZIP, folder, or files
Govilo To Go by the numbers
- 1 all-time installs (skills.sh)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hau823823/govilo-to-go --skill gen-paylink-goviloAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | February 13, 2026 |
| Repository | hau823823/govilo-to-go ↗ |
What it does
Uploads ZIPs, folders, or files to Govilo R2 storage and generates a paid unlock link priced in USDC via the Govilo Bot API.
Files
Govilo To Go
Before Running
Always ask the user for these values before executing the CLI — never guess or use placeholders:
1. title — What is the product name? 2. price — How much to charge (in USDC)? 3. description — Short description of the product (optional, but always ask)
CLI Command
cd skills/govilo-to-go
uv run --env-file ../../.env create-link \
--input <path> \
--title "Product Name" \
--price "5.00" \
--address "0x..." \
--description "optional"--input accepts ZIP file, folder, or individual files (repeatable). Non-ZIP inputs are auto-packaged.
All output is JSON {"ok": true/false, ...} with exit code 1 on failure.
Parameters
| Param | Required | Source | Description |
|---|---|---|---|
--input | Yes | CLI (repeatable) | ZIP, folder, or file paths |
--title | Yes | CLI | Product title |
--price | Yes | CLI | Price in USDC |
--address | No | CLI > SELLER_ADDRESS env | Seller EVM wallet |
--description | No | CLI | Product description |
Workflow
1. Validate config (API Key + seller address) 2. Package inputs → ZIP (if not already ZIP) 3. POST /api/v1/bot/uploads/presign → get upload_url + session_id 4. PUT upload_url → upload ZIP to R2 5. POST /api/v1/bot/items → get unlock_url
File Limits
- Max ZIP size: 20 MB
- Max files in ZIP: 20
Setup
Two values are required:
| Variable | Required | Description |
|---|---|---|
GOVILO_API_KEY | Yes | Bot API key from https://govilo.xyz/ |
SELLER_ADDRESS | Yes* | EVM wallet address on Base chain |
*SELLER_ADDRESS can also be passed via --address CLI parameter.
See references/setup-guide.md for step-by-step registration and wallet setup instructions.
API Reference
See references/bot-api-quick-ref.md for Bot API endpoints and error codes.
[project]
name = "govilo-to-go"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["requests"]
[project.scripts]
create-link = "scripts.workflow_create:main"
[project.optional-dependencies]
dev = ["pytest"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["scripts"]
[tool.pytest.ini_options]
testpaths = ["tests"]
Bot API Quick Reference
Base URL: https://api.unlock.govilo.xyz
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/bot/uploads/presign | Bearer sk_live_xxx | Get presigned upload URL |
| POST | /api/v1/bot/items | Bearer sk_live_xxx | Confirm upload and create item |
Presign Request
POST /api/v1/bot/uploads/presign
{"seller_address": "0x..."}Response: upload_url, session_id, object_path, expires_at
Upload
PUT <upload_url>
Content-Type: application/zip
Body: <raw ZIP bytes>Create Item Request
POST /api/v1/bot/items
{"session_id": "...", "title": "...", "price": "5.00", "description": "..."}Response: id, unlock_url, file_count, total_size, upload_status
Error Codes
| Code | HTTP | Message |
|---|---|---|
| 809108001 | 401 | invalid api key |
| 809108002 | 401 | api key has been revoked |
| 809108003 | 401 | api key has expired |
| 809108005 | 429 | daily api key usage limit exceeded |
| 809108006 | 429 | api key rate limit exceeded |
| 809104001 | 404 | upload session not found |
| 809104002 | 410 | upload session expired |
| 809104003 | 404 | file not found |
Setup Guide
Two values are required before using this tool:
GOVILO_API_KEY— Bot API authentication tokenSELLER_ADDRESS— Your wallet address on Base chain
1. API Key
Get Your API Key
1. Go to https://govilo.xyz/ 2. Sign up for an account (Gmail auth login) 3. Open the left sidebar menu → click Settings 4. Scroll to the API Key section → click Manage API Key 5. Create a new key and copy it (format: sk_live_xxx)
Configure
Add to project root .env:
GOVILO_API_KEY=sk_live_xxx
The CLI loads .env via --env-file ../../.env (relative to skills/govilo-to-go/).
Verify
cd skills/govilo-to-go uv run --env-file ../../.env create-link --help
If the key is missing or invalid, the tool outputs:
{"ok": false, "error": "API Key not configured. ..."}
2. Seller Address
Requirements
- Chain: Base (Base Mainnet only, Chain ID: 8453)
- Format: EVM address —
0x+ 40 hex characters - Example:
0x1234567890abcdef1234567890abcdef12345678
Get Your Address
MetaMask:
1. Install MetaMask browser extension (https://metamask.io/) 2. Create or import a wallet 3. Switch network to Base (add if not listed: Chain ID 8453, RPC https://mainnet.base.org) 4. Click the account name at the top to copy your address
Coinbase Wallet:
1. Install Coinbase Wallet (https://www.coinbase.com/wallet) 2. Create or import a wallet 3. Switch network to Base 4. Tap Receive → copy your address
Configure
Option A — Environment variable (recommended for repeated use):
Add to project root .env:
SELLER_ADDRESS=0xYourWalletAddress
Option B — CLI parameter (per-command override):
create-link --address "0xYourWalletAddress" ...
CLI --address takes priority over SELLER_ADDRESS env var.
Tip: You can also set a Default Payout Address on the Govilo website
(Settings → Default Payout Address → paste your 0x... address → Save).This address is pre-filled when creating new links on the web UI, but the CLI
still requiresSELLER_ADDRESSenv var or--addressparameter.
from pathlib import Path
import requests
class ApiError(Exception):
def __init__(self, message: str, code: int | None = None):
super().__init__(message)
self.code = code
class GoviloClient:
def __init__(self, api_key: str, base_url: str):
self._base_url = base_url.rstrip("/")
self._headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
def _check_response(self, resp: requests.Response) -> dict:
body = resp.json()
if body.get("code", 0) != 0:
raise ApiError(body.get("msg", "unknown error"), body.get("code"))
return body["data"]
def presign(self, seller_address: str) -> dict:
resp = requests.post(
f"{self._base_url}/api/v1/bot/uploads/presign",
headers=self._headers,
json={"seller_address": seller_address},
)
return self._check_response(resp)
def upload(self, upload_url: str, zip_path: Path) -> None:
with open(zip_path, "rb") as f:
resp = requests.put(
upload_url,
headers={"Content-Type": "application/zip"},
data=f,
)
if resp.status_code != 200:
raise ApiError(f"Upload failed: HTTP {resp.status_code}")
def create_item(
self,
session_id: str,
title: str,
price: str,
description: str = "",
) -> dict:
resp = requests.post(
f"{self._base_url}/api/v1/bot/items",
headers=self._headers,
json={
"session_id": session_id,
"title": title,
"price": price,
"description": description,
},
)
return self._check_response(resp)
from dataclasses import dataclass
import os
BASE_URL = "https://api.unlock.govilo.xyz"
class ConfigError(Exception):
pass
@dataclass(frozen=True)
class Config:
api_key: str
seller_address: str
base_url: str = BASE_URL
def load_config(
cli_api_key: str | None = None,
cli_address: str | None = None,
) -> Config:
api_key = cli_api_key or os.environ.get("GOVILO_API_KEY")
if not api_key:
raise ConfigError(
"API Key not configured. See references/setup-guide.md for registration steps. "
"Register at https://govilo.xyz/ then set GOVILO_API_KEY=sk_live_xxx"
)
seller_address = cli_address or os.environ.get("SELLER_ADDRESS")
if not seller_address:
raise ConfigError(
"Seller address required (Base chain). Use --address 0x... or set SELLER_ADDRESS env var. "
"See references/setup-guide.md for wallet setup"
)
return Config(api_key=api_key, seller_address=seller_address)
import tempfile
import zipfile
from pathlib import Path
MAX_ZIP_SIZE = 20 * 1024 * 1024 # 20 MB
MAX_FILE_COUNT = 20
class PackageError(Exception):
pass
def _validate_zip(zp: Path) -> None:
if zp.stat().st_size > MAX_ZIP_SIZE:
raise PackageError(f"ZIP file exceeds 20 MB limit")
with zipfile.ZipFile(zp) as zf:
if len(zf.namelist()) > MAX_FILE_COUNT:
raise PackageError(f"ZIP contains more than {MAX_FILE_COUNT} files")
def _zip_paths(paths: list[Path], dest: Path) -> None:
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
for p in paths:
zf.write(p, p.name)
def package(inputs: list[str]) -> Path:
paths = [Path(p) for p in inputs]
for p in paths:
if not p.exists():
raise PackageError(f"Path not found: {p}")
# Single .zip file — passthrough
if len(paths) == 1 and paths[0].suffix == ".zip":
_validate_zip(paths[0])
return paths[0]
# Single directory — zip its contents
if len(paths) == 1 and paths[0].is_dir():
files = [f for f in paths[0].rglob("*") if f.is_file()]
if len(files) > MAX_FILE_COUNT:
raise PackageError(f"Directory contains more than {MAX_FILE_COUNT} files")
dest = Path(tempfile.mktemp(suffix=".zip"))
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
for f in files:
zf.write(f, f.relative_to(paths[0]))
_validate_zip(dest)
return dest
# Multiple files — zip them together
if len(paths) > MAX_FILE_COUNT:
raise PackageError(f"Input exceeds {MAX_FILE_COUNT} files limit")
dest = Path(tempfile.mktemp(suffix=".zip"))
_zip_paths(paths, dest)
_validate_zip(dest)
return dest
import argparse
import json
import sys
from pathlib import Path
from scripts.api_client import GoviloClient, ApiError
from scripts.config import load_config, ConfigError
from scripts.packager import package, PackageError
def _output(data: dict) -> None:
print(json.dumps(data, ensure_ascii=False))
def _fail(error: str, code: int | None = None) -> None:
out = {"ok": False, "error": error}
if code is not None:
out["code"] = code
_output(out)
sys.exit(1)
def main() -> None:
parser = argparse.ArgumentParser(description="Upload files and create Govilo unlock link")
parser.add_argument("--input", required=True, action="append", dest="inputs", help="ZIP, folder, or file path (repeatable)")
parser.add_argument("--title", required=True, help="Product title")
parser.add_argument("--price", required=True, help="Price in USDC (e.g. 5.00)")
parser.add_argument("--address", default=None, help="Seller EVM wallet address (overrides SELLER_ADDRESS env)")
parser.add_argument("--description", default="", help="Product description")
args = parser.parse_args()
try:
cfg = load_config(cli_api_key=None, cli_address=args.address)
except ConfigError as e:
_fail(str(e))
try:
zip_path = package(args.inputs)
except PackageError as e:
_fail(str(e))
is_temp = zip_path not in [Path(p) for p in args.inputs]
try:
client = GoviloClient(api_key=cfg.api_key, base_url=cfg.base_url)
try:
presign_data = client.presign(cfg.seller_address)
client.upload(presign_data["upload_url"], zip_path)
item_data = client.create_item(
session_id=presign_data["session_id"],
title=args.title,
price=args.price,
description=args.description,
)
except ApiError as e:
_fail(str(e), code=e.code)
_output({
"ok": True,
"unlock_url": item_data["unlock_url"],
"item_id": item_data["id"],
"file_count": item_data["file_count"],
"total_size": item_data["total_size"],
})
finally:
if is_temp:
zip_path.unlink(missing_ok=True)