From dd9817a62e70d62e999c6ea1bdb1e7fdc83746e2 Mon Sep 17 00:00:00 2001 From: Naniko0213 <2018302141152@whu.edu.cn> Date: Fri, 3 Apr 2026 11:46:01 +0800 Subject: [PATCH 1/5] feat: pexo-agent v0.3.5 - credit error handling & entitlements - Add pexo-entitlements.sh: new script for checking account credit status - SKILL.md v0.3.5: add credit error handling section with fallback purchase link - Credit error detection in Step 1 (create) and Step 3 (send message) - Split Step 8 into credit vs non-credit failure paths - Update video duration range to 5-120s - Update _common.sh, pexo-chat.sh and other scripts for entitlements support Made-with: Cursor --- skills/pexo-agent/SKILL.md | 51 ++++- skills/pexo-agent/_meta.json | 5 + .../pexo-agent/references/TROUBLESHOOTING.md | 210 ++++++++++-------- skills/pexo-agent/scripts/_common.sh | 102 ++++++++- skills/pexo-agent/scripts/pexo-asset-get.sh | 5 +- skills/pexo-agent/scripts/pexo-chat.sh | 17 +- skills/pexo-agent/scripts/pexo-doctor.sh | 6 +- .../pexo-agent/scripts/pexo-entitlements.sh | 63 ++++++ .../pexo-agent/scripts/pexo-project-create.sh | 11 +- skills/pexo-agent/scripts/pexo-project-get.sh | 31 +-- .../pexo-agent/scripts/pexo-project-list.sh | 2 +- skills/pexo-agent/scripts/pexo-upload.sh | 4 +- 12 files changed, 353 insertions(+), 154 deletions(-) create mode 100644 skills/pexo-agent/_meta.json mode change 100755 => 100644 skills/pexo-agent/scripts/_common.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-asset-get.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-chat.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-doctor.sh create mode 100644 skills/pexo-agent/scripts/pexo-entitlements.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-project-create.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-project-get.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-project-list.sh mode change 100755 => 100644 skills/pexo-agent/scripts/pexo-upload.sh diff --git a/skills/pexo-agent/SKILL.md b/skills/pexo-agent/SKILL.md index e64d4b1..ad1956f 100644 --- a/skills/pexo-agent/SKILL.md +++ b/skills/pexo-agent/SKILL.md @@ -16,9 +16,9 @@ requires: - curl - jq - file +version: "0.3.5" metadata: author: pexoai - version: "0.3.4" --- # Pexo Agent @@ -110,7 +110,12 @@ Follow these steps in order. ``` Step 1. Create project. Run: pexo-project-create.sh "brief description" - Save the returned project_id. + If the command succeeds: save the returned project_id. + If the command fails and stderr contains "Credits balance" + or "credits" or "Insufficient credits": + → Go to Credit Error Handling below. + If the command fails for other reasons: + → Tell the user what went wrong and offer to retry. Step 2. Upload files (if user provided any images/videos/audio). Run: pexo-upload.sh @@ -121,6 +126,11 @@ Step 2. Upload files (if user provided any images/videos/audio). Step 3. Send user's message to Pexo. Run: pexo-chat.sh "{user's exact words} asset_id" Copy the user's words exactly. Only add asset tags for uploaded files. + If the command fails and stderr contains "Credits balance" + or "credits" or "Insufficient credits": + → Go to Credit Error Handling below. + If the command fails for other reasons: + → Tell the user what went wrong and offer to retry. Step 4. Notify the user (in the user's language). Your message must contain these three items: @@ -197,11 +207,13 @@ Step 7. Deliver the final video. Step 8. Handle failure. 8a. Read the nextActionHint field from the JSON. - 8b. Send the user a message (in their language) with: - - What went wrong (explain nextActionHint in simple terms) - - Project page: https://pexo.ai/project/{project_id} - - Help guide: https://pexo.ai/connect/openclaw - - Offer to retry. + 8b. Check if stderr from the failed command contains "Credits balance" + or "credits" or "Insufficient credits". + If yes → Go to Credit Error Handling below. + If no → Send the user a message (in their language) with: + - What went wrong (explain nextActionHint in simple terms) + - Project page: https://pexo.ai/project/{project_id} + - Offer to retry. Step 9. Timeout. @@ -216,6 +228,24 @@ Step 9. Timeout. Stop polling. Wait for user instructions. ``` +### Credit Error Handling + +When any command fails and stderr contains credit-related information +(look for: "Credits balance", "credits", or "Insufficient credits"): + +``` +Step A. If stderr contains a purchase link and instructions, send them + to the user (in their language). + +Step B. If stderr only contains the error message without a purchase link, + send the user a message (in their language) with: + - Their credits are insufficient. + - To add credits: visit https://pexo.ai/home + → click Credits (top-right) → Buy Credits → Extra Credits. + +Step C. After the user confirms they have added credits, retry the failed step. +``` + ### Revising an Existing Video ``` @@ -274,19 +304,20 @@ Tags are mandatory. Bare asset IDs in pexo-chat.sh messages are ignored by Pexo. | Script | Usage | Returns | |---|---|---| -| `pexo-project-create.sh` | `[project_name]` or `--name ` | `project_id` string | +| `pexo-project-create.sh` | `[project_name]` or `--name ` | `project_id` string. On `429`, credit info printed to stderr. | | `pexo-project-list.sh` | `[page_size]` or `--page --page-size ` | Projects JSON | | `pexo-project-get.sh` | ` [--full-history]` | JSON with `nextAction`, `nextActionHint`, `recentMessages` | | `pexo-upload.sh` | ` ` | `asset_id` string | -| `pexo-chat.sh` | ` [--choice ] [--timeout ]` | Acknowledgement JSON (async) | +| `pexo-chat.sh` | ` [--choice ] [--timeout ]` | Acknowledgement JSON (async). On `429`/`412` or credit errors, error info printed to stderr. | | `pexo-asset-get.sh` | ` ` | JSON with video details and `url` field | +| `pexo-entitlements.sh` | (no args) | JSON with `credits` and `plan` info. Called automatically by other scripts on credit errors. | | `pexo-doctor.sh` | (no args) | Diagnostic report | --- ## Pexo Capabilities -- Output: 5–60 second videos, aspect ratios 16:9 / 9:16 / 1:1 +- Output: 5–120 second videos, aspect ratios 16:9 / 9:16 / 1:1 - Production time: ~15–20 minutes for a 15s video, longer for complex/longer videos - Supported uploads: Images (jpg, png, webp, bmp, tiff, heic), Videos (mp4, mov, avi), Audio (mp3, wav, aac, m4a, ogg, flac) diff --git a/skills/pexo-agent/_meta.json b/skills/pexo-agent/_meta.json new file mode 100644 index 0000000..2caaa3e --- /dev/null +++ b/skills/pexo-agent/_meta.json @@ -0,0 +1,5 @@ +{ + "ownerId": "kn790mem6k7hbqd9xaakfbwbkx81pe1x", + "slug": "pexoai-agent", + "version": "0.3.5" +} \ No newline at end of file diff --git a/skills/pexo-agent/references/TROUBLESHOOTING.md b/skills/pexo-agent/references/TROUBLESHOOTING.md index 718c5f8..42b48b4 100644 --- a/skills/pexo-agent/references/TROUBLESHOOTING.md +++ b/skills/pexo-agent/references/TROUBLESHOOTING.md @@ -1,64 +1,45 @@ # Troubleshooting -This file describes the actual failure modes of the shell scripts under `infra/docs/agent-skills/pexo-video/scripts`. - ## Script Exit Behavior - Exit `0`: success - Exit `1`: request/transport/backend failure - Exit `2`: local usage error (missing args, invalid flags, invalid local input) -On request failure, the shared layer now prints compact JSON to `stderr`, for example: +On request failure, scripts print compact JSON to `stderr`, for example: ```json -{"ok":false,"httpCode":429,"businessCode":400001,"message":"Daily creation limit reached. Contact support email for more access."} +{"ok":false,"httpCode":429,"message":"Daily creation limit reached. Contact support email for more access."} ``` Fields you may see: - `httpCode`: the real HTTP status code returned to the script -- `businessCode`: nested backend business code when the BFF wrapped it (for example `400001`) - `error`: auth/proxy error code such as `INVALID_API_KEY` or `INTERNAL_ERROR` - `message`: the most useful user-facing message extracted from the response - `details`: extra backend detail when available -## Important Response Shapes - -The scripts hit two kinds of frontend endpoints, and the error body shape is different: - -### `/api/biz/*` routes +When the error is credit-related (`httpCode` 429 or 412 from `pexo-project-create.sh` or `pexo-chat.sh`), the script automatically fetches the user's credit balance and appends two extra lines to stderr: -These usually return a wrapper: - -```json -{"code":429,"message":"Too Many Requests","data":{"code":400001,"message":"..."}} +``` +Credits balance: 0 — your account has no available credits. +To purchase credits: visit https://pexo.ai/home → click Credits (top-right) → Buy Credits → Extra Credits ``` -The shared layer unwraps success bodies and, on failure, promotes the nested `data.message` / `data.code` into the compact `stderr` JSON. - -### `/api/chat` - -This is an SSE endpoint, not a normal JSON endpoint. - -- Success means the SSE stream was opened. -- `pexo-chat.sh` only waits for the initial `: stream opened` acknowledgement, then disconnects intentionally. -- On non-auth failure, the current frontend route returns a generic body like `{"error":"Chat error: 412"}` and preserves the HTTP status. - -That means for `pexo-chat.sh`, the status code is often more informative than the message text. ## Auth And Proxy Errors -These can happen on every script that talks to the frontend: +These can happen on every script that makes API calls: | HTTP | `error` | Meaning | What to do | |---|---|---|---| -| 401 | `INVALID_API_KEY` | API key is invalid or revoked | Update `PEXO_API_KEY` in `~/.pexo/config` | -| 401 | `MISSING_TOKEN` | Auth header missing | Check script environment / wrapper | -| 401 | `INTERNAL_ERROR` | Frontend BFF/proxy failed before completing the request | Treat as infra/backend issue, not as a bad API key | -| 409 | `SESSION_REPLACED` | JWT session was replaced by another login | Mostly relevant for browser/JWT auth, unusual for API-key usage | +| 401 | `INVALID_API_KEY` | API key is invalid or revoked | Update `PEXO_API_KEY` in `~/.pexo/config`. Get a new key at pexo.ai. | +| 401 | `MISSING_TOKEN` | The request was sent without an API key | Run `pexo-doctor.sh` to verify config. Make sure `~/.pexo/config` is sourced correctly. | +| 401 | `INTERNAL_ERROR` | The service failed to process the request before authentication completed | This is a temporary service issue, not a problem with the API key. Wait a moment and retry; if it persists, contact support. | +| 409 | `SESSION_REPLACED` | This API key's session was invalidated by a new login elsewhere | Unusual for API-key usage. Retry the command. If it keeps happening, regenerate the API key at pexo.ai. | If the message says `Invalid API key`, it is an auth problem. -If the body says `error=INTERNAL_ERROR`, do not tell the user to rotate the key first; the proxy/backend path may simply be down. +If the body says `error=INTERNAL_ERROR`, do not tell the user to rotate the key first; the service may simply be temporarily down. ## Script-Specific Errors @@ -66,23 +47,24 @@ If the body says `error=INTERNAL_ERROR`, do not tell the user to rotate the key Real statuses: -- `400`: invalid `project_name` (empty after local processing should no longer happen, too long still can) -- `401`: auth failure -- `429`: quota/business limit - - `businessCode=400001`: daily creation limit reached - - `businessCode=400002`: user already has an active project and must wait -- `500`: backend/internal failure +- `400`: project name is too long. Ask the user to use a shorter name and retry. +- `401`: auth failure — see Auth and Proxy Errors above. +- `429`: creation limit reached — could be any of: + - User already has an active project running (must wait for it to finish) + - Insufficient credits to start a new project + The script automatically fetches and prints the credit balance + top-up URL after any `429`. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. Notes: -- The script now defaults the project name to `"Untitled"` when no name is provided, because the backend requires `project_name`. +- If no project name is provided, the script defaults to `"Untitled"`. ### `pexo-project-list.sh` Real statuses: -- `401`: auth failure -- `500`: backend/internal failure +- `401`: auth failure — see Auth and Proxy Errors above. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. Notes: @@ -93,15 +75,15 @@ Notes: Real statuses from the first project fetch: -- `401`: auth failure -- `404`: project not found -- `500`: backend/internal failure +- `401`: auth failure — see Auth and Proxy Errors above. +- `404`: the project does not exist or has been deleted. Verify the project_id; if correct, start a new project. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. -Secondary history fetches (`/history`) can also fail with: +Subsequent status fetches can also fail with: -- `401`: auth failure -- `404`: project not found -- `500`: backend/internal failure +- `401`: auth failure — see Auth and Proxy Errors above. +- `404`: project not found. Same action as above. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. ### `pexo-upload.sh` @@ -109,29 +91,24 @@ This script has three phases, and the failure source matters. #### Phase 1: upload credential -Endpoint: `POST /api/biz/projects/:project_id/assets/upload-credential` - Real statuses: -- `400`: invalid `file_name` / `file_size` -- `401`: auth failure -- `500`: backend/internal failure +- `400`: the file name or file size is invalid. Check that the file exists and is not empty; rename it if it contains special characters. +- `401`: auth failure — see Auth and Proxy Errors above. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. Notes: -- This endpoint does not currently perform a strict project existence check before creating the upload credential. -- The script now rejects unsupported extensions locally, matching the backend allowlist: +- The script rejects unsupported extensions locally. Supported formats: - Images: `jpg`, `jpeg`, `png`, `webp`, `bmp`, `tiff`, `heic`, `heif` - Videos: `mp4`, `mov`, `avi` - Audio: `mp3`, `wav`, `aac`, `m4a`, `ogg`, `flac` -#### Phase 2: raw PUT to the presigned URL - -This phase does not go through the BFF wrapper. +#### Phase 2: file transfer Possible failures: -- `4xx/5xx` from object storage or CDN +- `4xx/5xx`: the file storage service rejected the upload. Check network connectivity and retry. If the problem persists, contact support at pexo.ai. The script surfaces this directly as: @@ -141,75 +118,108 @@ Error: upload failed with HTTP #### Phase 3: finalize -Endpoint: `POST /api/biz/projects/:project_id/assets/:asset_id/finalize` +Real statuses: + +- `400`: the file was rejected — possible reasons: file exceeds the size limit, file format is not supported, or the file content does not match its extension. Convert or compress the file and re-upload from scratch using `pexo-upload.sh`. +- `401`: auth failure — see Auth and Proxy Errors above. +- `404`: the file record was not found. The upload session may have been cleaned up. Re-upload from scratch using `pexo-upload.sh`. +- `412`: the upload session has already expired or been completed. Re-upload from scratch using `pexo-upload.sh`. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. + +### `pexo-chat.sh` Real statuses: -- `400`: invalid finalize body, file size limit hit, MIME/type mismatch, unsupported media type -- `401`: auth failure -- `404`: asset not found, or asset does not belong to the given project/user -- `412`: asset is not in `UPLOADING` state anymore -- `500`: internal/storage verification failure +- `400`: the message could not be sent due to invalid content. Check the message text; if the issue persists, start a new project. +- `401`: auth failure — see Auth and Proxy Errors above. +- `404`: the project does not exist or has been deleted. Start a new project. +- `412`: two possible causes: + - **Project no longer supported**: this project was created with an older version of Pexo's production system and cannot be continued. Start a new project. + - **Account billing issue**: the account's credits are frozen or suspended. The script automatically fetches and prints the credit balance + top-up URL. Direct the user to top up or contact support at pexo.ai. +- `429`: limit reached — could be insufficient credits or the project's video output limit. The script automatically fetches and prints the credit balance + top-up URL after a `429`. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. -Notes: +Business errors (credit-related): -- The script may omit `mime_type` during finalize if local MIME detection returns a value that is outside the backend allowlist. This lets the backend detect MIME from the uploaded file instead of failing on a bad local alias. +- `error=”credits.insufficient_credits_err”`: account has no available credits. `pexo-chat.sh` exits non-zero and prints compact JSON to `stderr`, for example: -### `pexo-chat.sh` +```json +{“ok”:false,”httpCode”:200,”message”:”Insufficient credits”,”error”:”credits.insufficient_credits_err”} +``` -Endpoint: `POST /api/chat`, which forwards to SSE `POST /api/projects/:project_id/messages` +Notes: + +- `pexo-chat.sh` is asynchronous. Success means the request was accepted, not that the video is done. +- For non-auth failures, use the HTTP status code as the primary signal. The automatically-appended credit balance lines are the most actionable hint. +- A successful `pexo-chat.sh` call should be followed by `pexo-project-get.sh` polling, typically every `60` seconds. + +### `pexo-entitlements.sh` Real statuses: -- `400`: invalid request body -- `401`: auth failure -- `404`: project not found -- `412`: agent schema version incompatible -- `429`: project video limit reached -- `500`: backend/internal failure +- `401`: auth failure — see Auth and Proxy Errors above. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. Notes: -- `pexo-chat.sh` is intentionally asynchronous now. Success means “the backend accepted the chat request and opened the SSE stream”, not “the video is done”. -- The frontend chat route currently hides detailed backend JSON on non-auth errors and returns only `Chat error: `. For this script, use the status code as the primary signal. -- A successful `pexo-chat.sh` call should be followed by `pexo-project-get.sh` polling, typically every `60` seconds. +- Returns JSON with `credits.availableCredits`, `credits.subscriptionCredits`, `credits.bonusCredits`, `credits.purchaseCredits`, and plan info. +- When `availableCredits` is `0`, the top-up URL is also printed to stderr. +- You generally do not need to call this script manually — `pexo-project-create.sh` and `pexo-chat.sh` call it automatically on `429`/`412` failures and include the balance in their error output. ### `pexo-asset-get.sh` Real statuses: -- `401`: auth failure -- `404`: asset not found, or asset/project ownership mismatch -- `500`: backend/internal failure +- `401`: auth failure — see Auth and Proxy Errors above. +- `404`: the file does not exist, or it belongs to a different project. Verify the asset_id and project_id. +- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai. Secondary download failures after metadata fetch: -- `403`: signed `downloadUrl` expired or object storage denied access -- `000`: local network failure while downloading the signed URL -- local filesystem write failure: `~/.pexo/tmp/` is not writable or disk is full +- `403`: the download link has expired. Re-run `pexo-asset-get.sh` to get a fresh link. +- `000`: network request failed before receiving a response. Check network connectivity and retry. +- local filesystem write failure: the temp directory (`~/.pexo/tmp/`) is not writable or the disk is full. Free up space or set `PEXO_TMP_DIR` to a writable path. Notes: -- The script now downloads `downloadUrl` into `~/.pexo/tmp/` (or `$PEXO_TMP_DIR`) and returns both `url` and `localPath`. +- The script downloads the file into `~/.pexo/tmp/` (or `$PEXO_TMP_DIR`) and returns both `url` and `localPath`. - If the asset metadata exists but `downloadUrl` is absent, the script returns `localPath: null`. ### `pexo-doctor.sh` -This script does not use the shared request wrapper, but its API check uses the same real endpoint: - - `200`: config and API key look healthy -- `401` + `INVALID_API_KEY`: bad key -- `401` + `INTERNAL_ERROR`: proxy/BFF problem -- `409`: JWT session conflict, unusual for API-key use -- `000`: no HTTP response received at all (network/DNS/TLS/connectivity issue) +- `401` + `INVALID_API_KEY`: API key is invalid or revoked. Update `PEXO_API_KEY` in `~/.pexo/config`. +- `401` + `INTERNAL_ERROR`: the service failed temporarily — not a key problem. Wait and retry. +- `409`: session conflict, unusual for API-key usage. Retry the command. +- `000`: no response received — network is unreachable or DNS failed. Check connectivity. ## Common Scenarios +### Insufficient credits — `429` or `412` with credit balance printed + +When `pexo-project-create.sh` or `pexo-chat.sh` fails with `429` or `412`, the script automatically fetches the credit balance and appends: + +``` +Credits balance: 0 — your account has no available credits. +To purchase credits: visit https://pexo.ai/home → click Credits (top-right) → Buy Credits → Extra Credits +``` + +If `availableCredits` is `0`: + +- Explain to the user that they have run out of credits. +- Guide them to purchase credits: visit https://pexo.ai/home, click Credits in the top-right corner → Buy Credits, then find Extra Credits. +- Do NOT retry the failed operation — it will fail again until credits are added. + +If `availableCredits` is non-zero but the error still appears: + +- The `429` is likely the concurrent-project limit: the user already has an active project running. +- Re-read the `message` field from the error JSON to confirm, then tell the user to wait for the current project to finish before creating a new one. + ### `pexo-chat.sh` returns success immediately This is expected. -The script only confirms that the SSE stream was accepted, then it disconnects on purpose. +The script only confirms that the request was accepted by the server, then exits. It does not stream progress or final results to the terminal. Next step: @@ -218,6 +228,18 @@ Next step: 2. Run `pexo-project-get.sh `. 3. Follow `nextAction`. +### `pexo-chat.sh` prints `credits.insufficient_credits_err` + +Meaning: + +- The account has no available credits. + +Action: + +1. Tell the user the account has no available credits for this chat request. +2. Direct them to top up credits at `https://pexo.ai/home`. +3. Do not retry `pexo-chat.sh` until credits are added; it will fail again with the same error. + ### `WAIT` lasts a long time This is normal for video generation. @@ -232,7 +254,7 @@ Practical guideline: Meaning: -- The run was marked `RUNNING`, but no worker stream is currently attached. +- The connection to the video generation service was interrupted. Action: @@ -264,6 +286,4 @@ Action: Inspect the error payload: - `error=INVALID_API_KEY`: fix the key -- `error=INTERNAL_ERROR`: treat it as proxy/backend trouble - -This distinction matters because the frontend BFF currently uses `401` for some internal proxy failures. +- `error=INTERNAL_ERROR`: treat it as a temporary service issue, not a key problem diff --git a/skills/pexo-agent/scripts/_common.sh b/skills/pexo-agent/scripts/_common.sh old mode 100755 new mode 100644 index 2767d86..e8656a4 --- a/skills/pexo-agent/scripts/_common.sh +++ b/skills/pexo-agent/scripts/_common.sh @@ -152,6 +152,63 @@ _pexo_emit_error() { return 1 } +_pexo_extract_sse_event_data() { + local response="${1:-}" + local target_event="${2:-error}" + + awk -v target_event="$target_event" ' + BEGIN { + in_event = 0 + data = "" + } + /^event:[[:space:]]*/ { + current = $0 + sub(/^event:[[:space:]]*/, "", current) + in_event = (current == target_event) + next + } + in_event && /^data:[[:space:]]*/ { + line = $0 + sub(/^data:[[:space:]]*/, "", line) + if (data == "") { + data = line + } else { + data = data "\n" line + } + next + } + in_event && /^$/ { + print data + exit + } + END { + if (in_event && data != "") { + print data + } + } + ' <<<"$response" +} + +_pexo_emit_sse_error() { + local payload="${1:-}" + + if _pexo_is_json "$payload"; then + jq -c ' + { + ok: false, + httpCode: 200, + message: (.error_message // .message // "request failed") + } + + (if (.error_code // "") != "" then {error: .error_code} else {} end) + + (if (.details // "") != "" then {details: .details} else {} end) + + (if (.hint // "") != "" then {hint: .hint} else {} end) + ' <<<"$payload" >&2 + return 1 + fi + + _pexo_emit_error 200 "" "${payload:-SSE returned an error event}" +} + _pexo_request_json() { local method="$1" local path="$2" @@ -213,7 +270,38 @@ _pexo_request_json() { rm -f "$body_file" "$header_file" "$err_file" } -# GET -> extracts .data from BFF wrapper when present +# _pexo_credit_hint: silently fetch the user's credit balance and emit a +# diagnostic line + top-up URL to stderr. Always returns 0 — never disrupts +# the caller's exit path or overwrites PEXO_LAST_HTTP_CODE. +_pexo_credit_hint() { + local _saved_code="${PEXO_LAST_HTTP_CODE:-0}" + local entitlements available + local topup_url="${PEXO_BASE_URL:-https://pexo.ai}/home" + + # Run in a subshell so set -e / PEXO_LAST_HTTP_CODE side-effects stay isolated. + entitlements=$( + set +e + pexo_require_config 2>/dev/null || exit 0 + _pexo_request_json GET "/api/biz/auth/entitlements" "" 2>/dev/null + ) || true + + export PEXO_LAST_HTTP_CODE="$_saved_code" + + [[ -n "$entitlements" ]] || return 0 + available=$(printf '%s' "$entitlements" \ + | jq -r '.credits.availableCredits // empty' 2>/dev/null) || true + [[ -n "$available" ]] || return 0 + + if [[ "$available" == "0" ]] || \ + { [[ "$available" =~ ^[0-9]+$ ]] && [[ "$available" -le 0 ]]; }; then + printf 'Credits balance: 0 — your account has no available credits.\n' >&2 + else + printf 'Credits balance: %s available.\n' "$available" >&2 + fi + printf 'To purchase credits: visit %s → click Credits (top-right) → Buy Credits → Extra Credits\n' "$topup_url" >&2 +} + +# GET -> unwraps response envelope when present pexo_get() { local path="$1" shift || true @@ -253,7 +341,7 @@ pexo_post_sse_ack() { -H "Accept: text/event-stream" \ -D "$header_file" \ -d "$body" \ - "${PEXO_BASE_URL}${path}" 2>"$err_file" | tee "$body_file" | sed '/^: stream opened$/q' >/dev/null + "${PEXO_BASE_URL}${path}" >"$body_file" 2>"$err_file" else curl -sS -N \ --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \ @@ -263,7 +351,7 @@ pexo_post_sse_ack() { -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -D "$header_file" \ - "${PEXO_BASE_URL}${path}" 2>"$err_file" | tee "$body_file" | sed '/^: stream opened$/q' >/dev/null + "${PEXO_BASE_URL}${path}" >"$body_file" 2>"$err_file" fi set -o pipefail @@ -278,6 +366,14 @@ pexo_post_sse_ack() { return 1 fi + local sse_error_payload + sse_error_payload=$(_pexo_extract_sse_event_data "$response" "error") + if [[ -n "$sse_error_payload" ]]; then + _pexo_emit_sse_error "$sse_error_payload" + rm -f "$body_file" "$header_file" "$err_file" + return 1 + fi + if [[ "$http_code" == "200" && "$content_type" == text/event-stream* && "$response" == *": stream opened"* ]]; then rm -f "$body_file" "$header_file" "$err_file" return 0 diff --git a/skills/pexo-agent/scripts/pexo-asset-get.sh b/skills/pexo-agent/scripts/pexo-asset-get.sh old mode 100755 new mode 100644 index 1f837f8..d03ba78 --- a/skills/pexo-agent/scripts/pexo-asset-get.sh +++ b/skills/pexo-agent/scripts/pexo-asset-get.sh @@ -14,9 +14,8 @@ Description: the local file path. Returns: - Asset JSON from /api/biz/projects/:project_id/assets/:asset_id - plus: - - url: original signed download URL + Asset JSON plus: + - url: signed download URL - localPath: downloaded local cache path, or null when downloadUrl is absent Common errors: diff --git a/skills/pexo-agent/scripts/pexo-chat.sh b/skills/pexo-agent/scripts/pexo-chat.sh old mode 100755 new mode 100644 index 7986b79..4cb9b0f --- a/skills/pexo-agent/scripts/pexo-chat.sh +++ b/skills/pexo-agent/scripts/pexo-chat.sh @@ -9,8 +9,8 @@ Usage: Description: Submit a message to an existing Pexo project. - This script does not keep the SSE stream open. It only waits until /api/chat - acknowledges the request by opening the stream, then it disconnects. + This script submits the message asynchronously. It waits until the server + acknowledges the request, then exits. If the message references uploaded assets, wrap each asset ID with one of: asset_id asset_id @@ -37,8 +37,10 @@ Common errors: 400 Invalid request body 401 Invalid API key or auth failure 404 Project not found - 412 Project agent version incompatible - 429 Project video limit reached + 412 Project agent version incompatible, or account credits frozen / billing issue + Credit balance and a top-up link are printed to stderr automatically. + 429 Project video limit reached, or insufficient credits to start production + Credit balance and a top-up link are printed to stderr automatically. 500 Backend/internal failure EOF } @@ -132,7 +134,12 @@ else '{project_id:$pid, timestamp:$ts, user_visible:true, native_inputs:{text:$msg}}') fi -pexo_post_sse_ack "/api/chat" "$body" "$timeout" +pexo_post_sse_ack "/api/chat" "$body" "$timeout" || { + if [[ "$PEXO_LAST_HTTP_CODE" == "429" || "$PEXO_LAST_HTTP_CODE" == "412" ]]; then + _pexo_credit_hint + fi + exit 1 +} jq -nc \ --arg pid "$pid" \ diff --git a/skills/pexo-agent/scripts/pexo-doctor.sh b/skills/pexo-agent/scripts/pexo-doctor.sh old mode 100755 new mode 100644 index f832fc9..3d4ed1e --- a/skills/pexo-agent/scripts/pexo-doctor.sh +++ b/skills/pexo-agent/scripts/pexo-doctor.sh @@ -20,7 +20,7 @@ Description: - required variables - local dependencies - network reachability - - API key/auth check against /api/biz/projects?page_size=1 + - API key/auth validity check Notes: API keys are expected to use the sk- prefix. @@ -97,7 +97,7 @@ if [[ -n "${PEXO_API_KEY:-}" ]]; then echo "$PASS PEXO_API_KEY is set: $masked" if [[ "$PEXO_API_KEY" != sk-* ]]; then echo "$WARN PEXO_API_KEY does not start with sk-" - echo " The current frontend API key validator recognizes keys with the sk- prefix." + echo " API keys are expected to start with sk-." fi else echo "$FAIL PEXO_API_KEY is not set" @@ -165,7 +165,7 @@ if [[ -n "${PEXO_BASE_URL:-}" && -n "${PEXO_API_KEY:-}" ]]; then errors=$((errors + 1)) elif [[ "$auth_error" == "INTERNAL_ERROR" ]]; then echo "$WARN API check returned HTTP 401 with INTERNAL_ERROR" - echo " This usually means the BFF/proxy failed before auth completed." + echo " This is a temporary service issue, not a problem with the API key. Wait and retry." echo " Message: $message" else echo "$FAIL API check returned HTTP 401" diff --git a/skills/pexo-agent/scripts/pexo-entitlements.sh b/skills/pexo-agent/scripts/pexo-entitlements.sh new file mode 100644 index 0000000..dca519e --- /dev/null +++ b/skills/pexo-agent/scripts/pexo-entitlements.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@" + +usage() { + cat <<'EOF' +Usage: + pexo-entitlements.sh + pexo-entitlements.sh -h | --help + +Description: + Fetch the current user's credit balance and plan entitlements. + Useful for proactively checking available credits before starting + production, or diagnosing why a previous request failed with 429. + +Returns (stdout): + JSON object with shape: + { + "userId": "...", + "credits": { + "availableCredits": 120, + "subscriptionCredits": 100, + "bonusCredits": 20, + "purchaseCredits": 0, + "frozenCredits": 0, + "lifetimeGranted": 1000, + "lifetimeConsumed": 880, + "lifetimeExpired": 0 + }, + "plan": { ... } + } + + When availableCredits is 0, a top-up URL is also printed to stderr. + +Common errors: + 401 Invalid API key or auth failure + 500 Backend/internal failure +EOF +} + +case "${1:-}" in + -h|--help) + usage + exit 0 + ;; +esac + +source "$(dirname "$0")/_common.sh" + +topup_url="${PEXO_BASE_URL:-https://pexo.ai}/home" + +result=$(pexo_get "/api/biz/auth/entitlements") + +available=$(printf '%s' "$result" \ + | jq -r '.credits.availableCredits // empty' 2>/dev/null) || true + +printf '%s\n' "$result" + +if [[ -n "$available" ]] && \ + { [[ "$available" == "0" ]] || \ + { [[ "$available" =~ ^[0-9]+$ ]] && [[ "$available" -le 0 ]]; }; }; then + printf '\nCredits balance: 0 — your account has no available credits.\n' >&2 + printf 'To purchase credits: visit %s → click Credits (top-right) → Buy Credits → Extra Credits\n' "$topup_url" >&2 +fi diff --git a/skills/pexo-agent/scripts/pexo-project-create.sh b/skills/pexo-agent/scripts/pexo-project-create.sh old mode 100755 new mode 100644 index bf49396..41e5677 --- a/skills/pexo-agent/scripts/pexo-project-create.sh +++ b/skills/pexo-agent/scripts/pexo-project-create.sh @@ -10,8 +10,7 @@ Usage: Description: Create a new Pexo project. - If no project name is provided, the script uses "Untitled" because the backend - requires project_name. + If no project name is provided, the script uses "Untitled". Returns: project_id string on stdout @@ -19,7 +18,8 @@ Returns: Common errors: 400 Invalid project name 401 Invalid API key or auth failure - 429 Daily creation limit or concurrent project limit reached + 429 Creation limit reached (concurrent-project limit or insufficient credits) + Credit balance and a top-up link are printed to stderr automatically. 500 Backend/internal failure EOF } @@ -69,7 +69,10 @@ fi [[ -n "$project_name" ]] || project_name="Untitled" body=$(jq -nc --arg n "$project_name" '{project_name: $n}') -result=$(pexo_post "/api/biz/projects" "$body") +result=$(pexo_post "/api/biz/projects" "$body") || { + [[ "$PEXO_LAST_HTTP_CODE" == "429" ]] && _pexo_credit_hint + exit 1 +} project_id=$(echo "$result" | jq -r '.projectId // empty') if [[ -z "$project_id" ]]; then diff --git a/skills/pexo-agent/scripts/pexo-project-get.sh b/skills/pexo-agent/scripts/pexo-project-get.sh old mode 100755 new mode 100644 index cd5a627..898bf68 --- a/skills/pexo-agent/scripts/pexo-project-get.sh +++ b/skills/pexo-agent/scripts/pexo-project-get.sh @@ -34,33 +34,9 @@ EOF # nextActionHint — plain-language instruction for what to do next # recentMessages — simplified last conversation round (when nextAction is RESPOND / DELIVER / FAILED / RECONNECT) # -# Raw status fields (status / executionStatus / serviceStatus) and meaningless -# progress values (executionProgress / stepProgress) are stripped from output. +# Internal status fields and progress values are stripped from output. # Callers should only use nextAction to decide what to do. # -# ── All (executionStatus × serviceStatus) combinations in practice ─────────── -# executionStatus: IDLE (DB default / no progress yet), RUNNING, FAILED, INTERRUPTED. -# COMPLETED is not used in production (Agent does not send "finished"). -# serviceStatus: IDLE (default or after ProcessExecution exits), PROCESSING (during ProcessExecution). -# -# | executionStatus | serviceStatus | Scenario | nextAction | -# |-----------------|---------------|----------|------------| -# | IDLE | IDLE | New project, or no active run; no message sent yet or previous run ended. | WAIT | -# | IDLE | PROCESSING | ProcessExecution just started, no progress event from Agent yet (brief). | WAIT | -# | RUNNING | IDLE | Run was reported RUNNING but worklet already exited (e.g. stream closed). Reconnect by sending a new message. | RECONNECT | -# | RUNNING | PROCESSING | Normal: Agent is producing, worklet is handling the stream. | WAIT | -# | INTERRUPTED | IDLE | Pexo waiting for input; no active ProcessExecution (user must send message or reconnect). | RESPOND | -# | INTERRUPTED | PROCESSING | Pexo waiting for input; ProcessExecution still open (stream waiting for reply). | RESPOND | -# | FAILED | IDLE | Run failed, worklet has exited. | FAILED | -# | FAILED | PROCESSING | Run failed, worklet defer not run yet (brief). | FAILED | -# -# nextAction mapping: -# FAILED — executionStatus=FAILED -# DELIVER — executionStatus=COMPLETED AND serviceStatus≠PROCESSING (COMPLETED not used in practice) -# RESPOND — executionStatus=INTERRUPTED -# RECONNECT — executionStatus=RUNNING AND serviceStatus=IDLE (should re-initiate conversation via pexo-chat.sh) -# WAIT — all other combinations -# # recentMessages format (simplified, actionable-only): # USER → {role, text} # message → {role, event:"message", text} @@ -104,7 +80,6 @@ while [[ $# -gt 0 ]]; do done # jq filter: simplify a raw messages array into actionable-only entries. -# ASSISTANT events not listed here (planning, progress, thinking, meta, voice…) are dropped. _SIMPLIFY_MSGS='[.[] | if (.role | ascii_downcase) == "user" then {role: "USER", text: (.content.native_inputs.text // null)} @@ -115,6 +90,7 @@ _SIMPLIFY_MSGS='[.[] | elif $evt == "final_video" then {role: "ASSISTANT", event: "final_video", assetId: ($d.final_video_id // null)} elif $evt == "preview_video" then {role: "ASSISTANT", event: "preview_video", assetIds: ($d.preview_video_ids // [])} elif $evt == "document" then {role: "ASSISTANT", event: "document", documentType: ($d.type // null), documentName: ($d.name // null)} + elif $evt == "attachment" then {role: "ASSISTANT", event: "attachment", assetIds: ($d.attachment_ids // [])} else empty end end @@ -126,7 +102,7 @@ _raw=$(pexo_get "/api/biz/projects/${pid}") exec_status=$(echo "$_raw" | jq -r '.executionStatus // ""') svc_status=$(echo "$_raw" | jq -r '.serviceStatus // ""') -# Strip raw status fields and meaningless progress values from the output project object +# Strip internal status fields from the output project object project=$(echo "$_raw" | jq 'del(.status, .executionStatus, .serviceStatus, .executionProgress, .stepProgress)') # ── Full history mode (bypass nextAction logic) ─────────────────────────────── @@ -152,7 +128,6 @@ elif [[ "$exec_status" == "RUNNING" && "$svc_status" == "IDLE" ]]; then next_action="RECONNECT" hint="Connection may have been lost. Re-initiate the conversation by sending a new message via pexo-chat.sh." else - # IDLE+IDLE, IDLE+PROCESSING, RUNNING+PROCESSING next_action="WAIT" hint="Production is in progress. Poll again in 60 seconds." fi diff --git a/skills/pexo-agent/scripts/pexo-project-list.sh b/skills/pexo-agent/scripts/pexo-project-list.sh old mode 100755 new mode 100644 index 75bb150..d11594b --- a/skills/pexo-agent/scripts/pexo-project-list.sh +++ b/skills/pexo-agent/scripts/pexo-project-list.sh @@ -16,7 +16,7 @@ Options: --page-size Page size (default: 20, effective max: 100) Returns: - Projects JSON from /api/biz/projects + Projects JSON Common errors: 401 Invalid API key or auth failure diff --git a/skills/pexo-agent/scripts/pexo-upload.sh b/skills/pexo-agent/scripts/pexo-upload.sh old mode 100755 new mode 100644 index 86c60c4..eaede81 --- a/skills/pexo-agent/scripts/pexo-upload.sh +++ b/skills/pexo-agent/scripts/pexo-upload.sh @@ -10,7 +10,7 @@ Usage: Description: Upload a local media file to a project in three steps: 1. Request upload credential - 2. PUT bytes to the presigned URL + 2. Upload the file 3. Finalize the asset Supported file types: @@ -79,7 +79,7 @@ storage_path=$(echo "$cred" | jq -r '.storagePath') [[ -n "$asset_id" && "$asset_id" != "null" ]] || { echo "Error: upload credential missing assetId" >&2; echo "$cred" >&2; exit 1; } [[ -n "$storage_path" && "$storage_path" != "null" ]] || { echo "Error: upload credential missing storagePath" >&2; echo "$cred" >&2; exit 1; } -# Phase 2: PUT raw bytes to presigned URL +# Phase 2: upload file http_code=$(curl -sS -X PUT -H "Content-Type: $mime_type" \ --data-binary "@$filepath" -o /dev/null -w '%{http_code}' "$upload_url" 2>/dev/null || echo "000") From 6725827c6216a99ffcc65616dc869be25bc56b01 Mon Sep 17 00:00:00 2001 From: Naniko0213 <2018302141152@whu.edu.cn> Date: Fri, 3 Apr 2026 11:48:06 +0800 Subject: [PATCH 2/5] chore: bump version to 0.3.6 to sync with ClawHub Made-with: Cursor --- skills/pexo-agent/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/pexo-agent/SKILL.md b/skills/pexo-agent/SKILL.md index ad1956f..804dcaa 100644 --- a/skills/pexo-agent/SKILL.md +++ b/skills/pexo-agent/SKILL.md @@ -16,7 +16,7 @@ requires: - curl - jq - file -version: "0.3.5" +version: "0.3.6" metadata: author: pexoai --- From 93cd06a910694ae525db7f4325f74d1cce8ac1d7 Mon Sep 17 00:00:00 2001 From: Naniko0213 <2018302141152@whu.edu.cn> Date: Fri, 3 Apr 2026 11:57:48 +0800 Subject: [PATCH 3/5] chore: bump version to 0.3.7 to sync with ClawHub Made-with: Cursor --- skills/pexo-agent/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/pexo-agent/SKILL.md b/skills/pexo-agent/SKILL.md index 804dcaa..5a122f4 100644 --- a/skills/pexo-agent/SKILL.md +++ b/skills/pexo-agent/SKILL.md @@ -16,7 +16,7 @@ requires: - curl - jq - file -version: "0.3.6" +version: "0.3.7" metadata: author: pexoai --- From 1429e5a97b01c86f7c1b6af7e5d7c9d62e2a23f9 Mon Sep 17 00:00:00 2001 From: Naniko0213 <2018302141152@whu.edu.cn> Date: Fri, 3 Apr 2026 12:16:03 +0800 Subject: [PATCH 4/5] chore: bump version to 0.3.8 Made-with: Cursor --- skills/pexo-agent/SKILL.md | 2 +- skills/pexo-agent/_meta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/pexo-agent/SKILL.md b/skills/pexo-agent/SKILL.md index 5a122f4..76ee514 100644 --- a/skills/pexo-agent/SKILL.md +++ b/skills/pexo-agent/SKILL.md @@ -16,7 +16,7 @@ requires: - curl - jq - file -version: "0.3.7" +version: "0.3.8" metadata: author: pexoai --- diff --git a/skills/pexo-agent/_meta.json b/skills/pexo-agent/_meta.json index 2caaa3e..4978c1c 100644 --- a/skills/pexo-agent/_meta.json +++ b/skills/pexo-agent/_meta.json @@ -1,5 +1,5 @@ { "ownerId": "kn790mem6k7hbqd9xaakfbwbkx81pe1x", "slug": "pexoai-agent", - "version": "0.3.5" + "version": "0.3.8" } \ No newline at end of file From e346c7fd48325177d9e4ea107ea4b16bac69a2c6 Mon Sep 17 00:00:00 2001 From: Naniko0213 <2018302141152@whu.edu.cn> Date: Fri, 3 Apr 2026 12:18:11 +0800 Subject: [PATCH 5/5] chore: bump version to 0.3.9, fix display name Made-with: Cursor --- skills/pexo-agent/SKILL.md | 2 +- skills/pexo-agent/_meta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/pexo-agent/SKILL.md b/skills/pexo-agent/SKILL.md index 76ee514..c37a848 100644 --- a/skills/pexo-agent/SKILL.md +++ b/skills/pexo-agent/SKILL.md @@ -16,7 +16,7 @@ requires: - curl - jq - file -version: "0.3.8" +version: "0.3.9" metadata: author: pexoai --- diff --git a/skills/pexo-agent/_meta.json b/skills/pexo-agent/_meta.json index 4978c1c..7bda0ec 100644 --- a/skills/pexo-agent/_meta.json +++ b/skills/pexo-agent/_meta.json @@ -1,5 +1,5 @@ { "ownerId": "kn790mem6k7hbqd9xaakfbwbkx81pe1x", "slug": "pexoai-agent", - "version": "0.3.8" + "version": "0.3.9" } \ No newline at end of file