| name | viverse-pls-cli |
| description | Upload and replace 3D model assets to VIVERSE using pls-cli. Use when the task involves uploading .zip/.glb/.obj files to VIVERSE, replacing existing assets, managing model conversion, or running pls-cli commands against stage/prod environments. |
pls-cli — VIVERSE Model Upload/Replace CLI
Operational guide for AI agents running pls-cli to upload or replace 3D models on VIVERSE.
When to Activate
- User wants to upload a model file (.zip, .glb, .obj) to VIVERSE
- User wants to replace an existing asset by asset ID
- Running smoke tests or integration tests against stage API
- Debugging upload/conversion failures
0. Install pls-cli
Check if already installed
which pls-cli || ls ~/bin/pls-cli 2>/dev/null || ls /usr/local/bin/pls-cli 2>/dev/null
If found, skip this section.
Install from GitHub Releases (recommended)
Detect OS and architecture, then download the correct binary:
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
VERSION="v1.0.0"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
arm64|aarch64) ARCH="arm64" ;;
esac
BINARY="pls-cli-${OS}-${ARCH}"
URL="https://github.com/ViveportSoftware/pls-cli/releases/download/${VERSION}/${BINARY}"
mkdir -p ~/bin
curl -fsSL "$URL" -o ~/bin/pls-cli
chmod +x ~/bin/pls-cli
Windows: Download pls-cli-windows-amd64.exe from the releases page and add it to your PATH.
Add ~/bin to PATH (if not already)
echo $PATH | grep -q "$HOME/bin" || export PATH="$HOME/bin:$PATH"
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
Verify installation
pls-cli version
1. Before Running Commands
Credential safety rule
NEVER pass email or password as literal values in shell commands — they appear in logs.
Always read from env vars:
source .env
If .env doesn't exist, ask the user to set the variables in their terminal before proceeding.
2. Authentication
The CLI uses cookie-based auth stored in ~/.pls-cli/credentials.json.
There is no token env var — you must log in first with pls-cli login.
Login (saves credentials to ~/.pls-cli/credentials.json)
pls-cli login --stage \
--email="$PLS_CLI_TEST_EMAIL" \
--password="$PLS_CLI_TEST_PASSWORD"
pls-cli login \
--email="$PLS_CLI_TEST_EMAIL" \
--password="$PLS_CLI_TEST_PASSWORD"
Verify login succeeded
pls-cli status
Environment mismatch — handled automatically
If you run upload --stage but logged in for prod (or vice versa), the CLI will exit with an error before touching the API:
error: credentials are for prod environment, but --stage flag was provided
Re-run: pls-cli login --stage
You do not need to manually check the environment — the CLI enforces it.
3. Upload
pls-cli upload model.zip
pls-cli upload model.zip --group=<group-uuid>
pls-cli upload model.zip --group=<group-uuid> --stage
pls-cli upload model.glb \
--group=<group-uuid> \
--stage \
--ai-enhance \
--collider \
--resolution=high \
--collider-scale=5
pls-cli upload file1.zip file2.glb file3.obj --group=<group-uuid>
pls-cli upload model.zip --json
Upload flags reference
| Flag | Values | Default | Notes |
|---|
--group | UUID | auto-selected | Omit to use your first group automatically |
--stage | bool | false | Use staging environment |
--ai-enhance | bool | false | AI enhancement |
--collider | bool | false | Generate collision mesh |
--resolution | performance/balanced/high/ultra | balanced | - |
--collider-scale | 0.3/2/5/10/100 | 2.0 | - |
--secure | bool | false | Encryption |
--json | bool | false | Write JSON to stdout; human messages go to stderr |
--tags | comma-separated names | - | Auto-create missing tags and assign after upload |
4. Replace
pls-cli replace <old-asset-id> new-model.zip
pls-cli replace <old-asset-id> new-model.glb --stage
pls-cli replace <old-asset-id> new-model.obj --collider --collider-scale=10 --json
Replace shares the same flags as upload except --group (originId is provided instead). This includes --tags — pass comma-separated tag names to auto-create and assign tags after conversion.
5. Tag Management
Tags are labels you can attach to assets. You can create them, list them, and assign them to assets after upload.
tag create
pls-cli tag create --group=<group-uuid> "my-tag"
pls-cli tag create --group=<group-uuid> --stage "my-tag"
pls-cli tag create --group=<group-uuid> --json "my-tag"
tag list
pls-cli tag list --group=<group-uuid>
pls-cli tag list --group=<group-uuid> --stage
pls-cli tag list --group=<group-uuid> --json
tag assign
pls-cli tag assign --asset=<asset-uuid> <tag-uuid-1> <tag-uuid-2>
pls-cli tag assign --asset=<asset-uuid> --stage <tag-uuid-1>
pls-cli tag assign --asset=<asset-uuid> --json <tag-uuid-1> <tag-uuid-2>
Tag flags reference
| Command | Flag | Values | Notes |
|---|
tag create | --group | UUID | Required — group to create the tag in |
tag create | --stage | bool | Use staging environment |
tag create | --json | bool | Write JSON to stdout; human messages go to stderr |
tag list | --group | UUID | Required — group to list tags from |
tag list | --stage | bool | Use staging environment |
tag list | --json | bool | Write JSON to stdout; human messages go to stderr |
tag assign | --asset | UUID | Required — asset to assign tags to |
tag assign | --stage | bool | Use staging environment |
tag assign | --json | bool | Write JSON to stdout; human messages go to stderr |
--tags flag on upload and replace
Pass --tags to auto-create any missing tags and assign them to the asset after conversion:
pls-cli upload model.glb --group=<group-uuid> --tags=foo,bar
pls-cli replace <old-asset-id> new-model.glb --tags=foo,bar
pls-cli upload model.glb --group=<group-uuid> --tags=foo,bar --json
--tags accepts a comma-separated list of tag names. The CLI:
- Lists existing tags for the group
- Creates any tags that don't already exist
- Assigns all resolved tag UUIDs to the asset after conversion completes
6. Machine-Readable Output (--json)
Always pass --json when the result needs to be parsed programmatically.
Human-readable messages go to stderr; structured result goes to stdout.
Upload JSON output
{
"files": [
{
"file": "model.zip",
"assetId": "abc-123-uuid",
"status": "ready"
}
]
}
Upload JSON output (with --tags)
When --tags is used, the upload JSON output includes a "tags" field:
{
"files": [
{
"file": "model.zip",
"assetId": "abc-123-uuid",
"status": "ready"
}
],
"tags": [
{ "uuid": "tag-uuid-1", "name": "foo" },
{ "uuid": "tag-uuid-2", "name": "bar" }
]
}
tag create JSON output
{ "uuid": "tag-uuid-1", "name": "my-tag" }
tag list JSON output
{
"tags": [
{ "uuid": "tag-uuid-1", "name": "foo" },
{ "uuid": "tag-uuid-2", "name": "bar" }
]
}
tag assign JSON output
{ "assetId": "asset-uuid", "tagUuids": ["tag-uuid-1", "tag-uuid-2"] }
Replace JSON output
{
"originId": "old-asset-uuid",
"file": "new-model.glb",
"assetId": "new-asset-uuid",
"status": "ready"
}
Failure case
{
"files": [
{
"file": "bad-model.zip",
"assetId": "abc-123-uuid",
"status": "failed",
"failedType": "convert",
"error": "Model file corrupted",
"errorCode": "INVALID_MODEL"
}
]
}
Status values: "ready" (success) | "failed" (conversion failed)
Shell parsing example
result=$(pls-cli upload model.zip --json 2>/dev/null)
status=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin)['files'][0]['status'])")
asset_id=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin)['files'][0]['assetId'])")
7. What the CLI Does Internally
Understanding this helps debug failures:
1. Validate file (format, size, count)
2. POST /management/asset → get { id, uploadUrl }
3. PUT $uploadUrl (S3 direct upload, shows progress bar on stderr)
4. POST /management/asset/:id/convert
5. WebSocket wss://{domain}/management/user/ws → stream conversion progress
6. Exit 0 on "ready", exit 1 on "failed"
6.5. (optional) If --tags provided: resolve tag names → create missing tags → PUT /management/asset/:id/tags
Replace uses PUT /management/asset/:originId instead of POST at step 2.
8. Supported File Formats
| Format | Notes |
|---|
.zip | Can bundle multiple resources |
.glb | glTF binary |
.obj | Wavefront OBJ |
Max file count: 10 per upload call.
Max file size: 500 MB per file (FREE tier limit from API).
9. Running Tests
go test -race ./...
source .env
go test -v -timeout 300s -run '^TestStage' ./cmd/pls-cli/
Tests auto-skip when PLS_CLI_TEST_EMAIL / PLS_CLI_TEST_PASSWORD are unset.
10. Common Failures and Fixes
| Symptom | Cause | Fix |
|---|
401 Unauthorized | Expired token or missing cookie | Re-run pls-cli login |
credentials are for prod, but --stage was provided | Env mismatch at login vs upload | Re-login with the matching --stage flag |
| Error code 11 | Wrong password or malformed auth ticket | Check credentials |
| Error code 1105 | Binary built without correct client ID ldflags | Install the official release binary from GitHub Releases (see Section 0) |
| Error code 1108 | Scope not allowed | Don't pass extra --scopes |
Conversion status: "failed" | Model file corrupted or unsupported | Check failedType and errorCode in JSON output |
| Binary not found | pls-cli not installed | Install from GitHub Releases (see Section 0) |
| Dev build fails at login | No client ID burned in | Install the official release binary from GitHub Releases (see Section 0) |
11. Environments
| Env | API base | WS base | Login flag |
|---|
| Production | https://stream.viverse.com | wss://stream.viverse.com | (default) |
| Stage | https://stream-stage.viverse.com | wss://stream-stage.viverse.com | --stage |
Always match --stage between login and upload/replace. The CLI enforces this at runtime and will exit with a clear error if they don't match.