⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

version unknown

product-photo-studio

@beatra-ai⭐ 7 stars

Transform a real product photo into a studio-quality ecommerce image, lifestyle scene, or marketplace-ready hero shot. This AI product photography tool replaces backgrounds, improves lighting, and stages scenes while using the source photo and confirmed product details as the visual anchor. Create clean white-background listings, contextual lifestyle compositions, and premium ad visuals from a single phone snap for Amazon, Taobao, Shopify, and social media. Start from one product photo, combine several references, or refine a selected draft toward a polished listing image.

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐ 7 on GitHubGitHub ↗

New / niche

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README

AI Product Photography Skill

English | 简体中文 | 日本語

👉 See also Ecommerce Product Images Skill

Turn a real product photo into ecommerce studio photos and lifestyle scenes, from inside Claude Code, Cursor, Codex, Windsurf, Cline, or OpenClaw.

[!IMPORTANT] Rendering needs a Beatra account and uses credits. The skill itself is free to install.

QuestionAnswer
What it doesTransform a real product photo into a studio-quality ecommerce image, lifestyle scene, or marketplace-ready hero shot with clean backgrounds, professional lighting, and composition guided by confirmed product details.
ModelsQwen Image 2.0 Fast, Qwen Image 3.0, Qwen Image 3.0 Pro, Wan Image 2.7 Fast, Qwen Image 2.0 Pro, Wan Image 2.7 Pro, GPT Image 2.5 Flare, GPT Image 2.5 Sunburst, GPT Image 2, MiniMax Image 01 Live
RequirementsPython 3.10+ and an agent that loads SKILL.md
CostFree to install. Each render uses credits on your Beatra account, and paid steps run only when you ask for that exact render or approve its card.
Works withClaude Code, Cursor, Codex, Windsurf, Cline, OpenClaw

A cluttered kitchen-counter phone snapshot of a fictional rosehip face oil, OLUNEA (itself AI-generated as the demo input), turned into a white-background studio listing image with the label text and bottle shape unchanged. AI-generated with Beatra.

SkillEntry pointVersion
product-photo-studioSKILL.md0.3.1

This repository is published automatically from beatra-ai/beatra-skills. Report issues there.

Install

With the skills CLI:

npx skills add beatra-ai/ai-product-photography-skill

As a Claude Code plugin:

claude plugin marketplace add beatra-ai/ai-product-photography-skill
claude plugin install product-photo-studio@ai-product-photography-skill

As a Codex plugin:

codex plugin marketplace add beatra-ai/ai-product-photography-skill
codex plugin add product-photo-studio@ai-product-photography-skill

As a GitHub Copilot CLI plugin:

copilot plugin marketplace add beatra-ai/ai-product-photography-skill
copilot plugin install product-photo-studio@ai-product-photography-skill

As a Gemini CLI extension:

gemini extensions install https://github.com/beatra-ai/ai-product-photography-skill

With the GitHub CLI:

gh skill install beatra-ai/ai-product-photography-skill product-photo-studio

Or clone this repository and copy skills/product-photo-studio into ~/.claude/skills/ for Claude Code, ~/.cursor/skills/ for Cursor, ~/.agents/skills/ for Codex, ~/.codeium/windsurf/skills/ for Windsurf, ~/.cline/skills/ for Cline, or ~/.openclaw/skills/ for OpenClaw.

Or paste this into your agent:

Install the product-photo-studio skill from https://github.com/beatra-ai/ai-product-photography-skill (folder skills/product-photo-studio), then follow its SKILL.md to connect my Beatra account.

Examples

The same OLUNEA bottle from the phone snapshot placed in a marble bathroom-vanity lifestyle scene with soft window light, label and shape kept as in the source. AI-generated with Beatra.

Ask your agent:

Using ./olunea-counter.jpg as the product reference, place the same dropper bottle on a marble bathroom vanity with soft window light for a social post. Keep the label text, shape and color exactly as in the photo.

Prompt:

Professional lifestyle product photo for social media. Image 1 is the product reference: the dropper bottle standing on the counter. Match its shape, color, label, label text, and proportions closely, and keep the label text exactly as it appears in Image 1. Leave out the kitchen counter and all other objects from Image 1. Place only the bottle upright on a white marble bathroom vanity, facing the camera, slightly right of center, with a folded linen hand towel and a small sprig of dried rose hips softly out of focus in the background, smaller than the bottle. Soft diffused window light from the left, warm neutral color temperature, gentle highlight on the glass, shallow depth of field with the bottle sharp and the background soft, natural contact shadow falling to the right, clean product edges, consistent lighting direction, no halo or fringe. No added text, logos, or watermarks.

Technical Specifications & Output Limits

As of 2026-10-18. Live list: https://docs.beatra.ai/en/docs/models

  • Uploads — up to 100 MB per file; the per-model input limits below also apply
  • Image to image — 10 models (Qwen Image 2.0 Fast, Qwen Image 3.0, Qwen Image 3.0 Pro, Wan Image 2.7 Fast, Qwen Image 2.0 Pro, Wan Image 2.7 Pro, GPT Image 2.5 Flare, GPT Image 2.5 Sunburst, GPT Image 2, MiniMax Image 01 Live); PNG; up to 4 images per request; canvas up to 16.8 MP
    • 16.8 MP (4K tier): GPT Image 2.5 Flare, GPT Image 2.5 Sunburst, GPT Image 2 (1 image per request)
    • Input images: up to 4 per request, 10–20 MB each; BMP, GIF, JPEG, PNG, TIFF, WEBP
  • Image editing — 6 models (Qwen Image 2.0 Fast, Qwen Image 3.0, Qwen Image 3.0 Pro, Wan Image 2.7 Fast, Qwen Image 2.0 Pro, Wan Image 2.7 Pro); PNG; up to 4 images per request; canvas up to 4.2 MP
    • 4.2 MP (2K tier): Qwen Image 2.0 Fast, Qwen Image 3.0, Qwen Image 3.0 Pro, Wan Image 2.7 Fast, Qwen Image 2.0 Pro, Wan Image 2.7 Pro (up to 4 images per request)
    • Input images: up to 4 per request, 10–20 MB each; BMP, GIF, JPEG, PNG, TIFF, WEBP

Why use Beatra instead of calling model APIs directly

  • One account for every model this skill uses — Image to image 10; Image editing 6 (Qwen Image 2.0 Fast, Qwen Image 3.0, Qwen Image 3.0 Pro, Wan Image 2.7 Fast, Qwen Image 2.0 Pro, Wan Image 2.7 Pro, GPT Image 2.5 Flare, GPT Image 2.5 Sunburst, GPT Image 2, MiniMax Image 01 Live) (as of 2026-10-18), behind one Beatra sign-in; no keys from individual model providers to manage.
  • You decide what gets paid for — Paid renders run only when you ask for that exact render or approve its card, which shows the live credit estimate.
  • Direction before rendering — the skill plans, directs, and reviews the work, then renders it on Beatra.
  • Files you can verify — every file under skills/ matches the release archive on cdn.beatra.ai byte for byte, checked by SHA-256 before each push.
  • Updates you control — no self-updater; update with the tool you installed it with.
  • Runs in your agent — Claude Code, Cursor, Codex, Windsurf, Cline, OpenClaw.

What you get

  • The source product as the visual anchor — Carry the confirmed shape, color, label, proportions, materials, and included pieces from the source photo into each new background or scene.
  • Marketplace-oriented backgrounds — Create pure-white Amazon main images, clean Taobao listings, consistent Shopify catalogs, and lifestyle social posts from the same product photo.
  • Scene and lifestyle staging — Place the product in a contextual scene—kitchen counter, wooden table, marble shelf, or seas

// HOW IT'S BUILT

// FILES

18 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • skills/product-photo-studio/SKILL.md8.9 KBbf64bf0c9adeupstream ↗
    ---
    name: "product-photo-studio"
    description: "Transform a real product photo into a studio-quality ecommerce image, lifestyle scene, or marketplace-ready hero shot. This AI product photography tool replaces backgrounds, improves lighting, and stages scenes while using the source photo and confirmed product details as the visual anchor. Create clean white-background listings, contextual lifestyle compositions, and premium ad visuals from a single phone snap for Amazon, Taobao, Shopify, and social media. Start from one product photo, combine several references, or refine a selected draft toward a polished listing image."
    ---
    
    # AI Product Photo Studio
    
    Transform one real product photo into a studio-quality listing image, lifestyle
    scene, or marketplace-ready hero shot. Reuse decisions already present in the
    conversation and move by the shortest route that completes the requested image.
    
    ## Choose the route
    
    - **Clean background:** with one product photo, remove the original background
      and place the product on a clean white, light-gray, or studio-gradient
      background using `beatra.images.transform`. This is the default for
      marketplace main images.
    - **Scene and lifestyle:** with one product photo and a scene description,
      place the product in a contextual lifestyle setting—on a kitchen counter, a
      wooden table, a marble shelf, or a seasonal backdrop—using
      `beatra.images.transform`.
    - **Refine an accepted draft:** use `beatra.images.edit` with the accepted
      image as `images[0]` to fix a shadow, remove a reflection, adjust color
      temperature, or clean up a small defect without changing the composition.
    
    Follow [product routing](references/product-routing.md) for the precise branch
    and [scene craft](references/scene-craft.md) when turning the request into a
    visual specification anchored to the source product.
    
    ## Shape one product brief
    
    The hard input is a real product photo. Scene and lighting must not rewrite a
    confirmed color, accessory, or quantity.
    
    Reuse the user's product type, intended marketplace, background preference,
    and any style references. Ask only when a missing decision materially changes
    the result. For a standard marketplace main image, propose a clean white
    background as the default; for a lifestyle request, propose a scene that
    matches the product's category.
    
    Build the brief around:
    
    - the product itself—what it is, its category, and any key visual details
      (label text, brand logo, shape, color);
    - one target marketplace or use (Amazon, Taobao, Shopify, social media, ad
      campaign) when it determines format rules;
    - one background or scene direction (clean white, studio gradient, lifestyle
      context, seasonal);
    - ordered visual references when available (style inspiration, background
      reference, angle reference).
    
    If the user has already stated the target marketplace or background type, reuse
    it. If that choice is genuinely missing, propose the best default and include
    it in the single paid-call confirmation.
    
    ## Prepare the call
    
    Use only this Skill's bundled `scripts/mcp_client.py` for every remote MCP
    operation. The tool name is a CLI argument and the tool arguments are the JSON
    sent on stdin. Do not configure or call a host Beatra Connector, and do not use
    REST/OpenAPI as a fallback. For exact commands and troubleshooting, use
    [Bundled MCP Client diagnostics](references/mcp-connection.md).
    
    - Upload the product photo through the bundled client helpers first, then
      call `beatra.images.transform` with the uploaded artifact as the first
      ordered reference. Label the product image's role explicitly in the prompt
      so the model treats it as the visual anchor.
    - For a clean background, set an explicit square or marketplace-ratio canvas.
    - For a lifestyle scene, describe the scene, lighting direction, and surface
      material in the prompt while identifying the source product details that
      should carry into the result.
    - For an accepted draft, call `beatra.images.edit`. Use at most two normalized
      `edit_regions` on `image_index=0` for localized fixes; omit regions for a
      whole-image adjustment.
    
    Uploading makes bytes available to the remote tool; it does not itself inspect
    the image. Review only visual facts the host can actually see.
    
    Keep `model=auto` and `count=1` unless the user explicitly chooses otherwise.
    Call `beatra.models.list` only for a real model, availability, compatibility,
    or price decision. The detailed request shapes and examples are in
    [workflow](references/workflow.md).
    
    ## Confirm and execute once
    
    Planning and brief preparation are free. Before the paid image call, show and
    freeze the final prompt, ordered references, canvas, background or scene
    direction, model, controls, and output count. Merge any still-material
    high-impact choice into this one confirmation.
    
    After approval, create one stable opaque `client_request_id` for that exact
    logical request and submit it once. A changed prompt, reference or order,
    canvas, scene direction, model, count, or control is new paid work and needs a
    new confirmation and a new ID.
    
    ## Track, review, and deliver
    
    After receiving a `task_id`, poll only that task with `beatra.tasks.get`. If the
    ID is lost, use `beatra.tasks.list` to find candidates and verify the selected
    one with `tasks.get`. Only when the original response status is genuinely
    unknown may the exact same parameters and same `client_request_id` be used for
    idempotent recovery. Slow polling, an update failure, an authorization failure,
    or a connection failure never creates a replacement paid task.
    
    Use `beatra.tasks.cancel` only when the user asks. If cancellation returns
    `409`, continue tracking the original task. See [review and
    recovery](references/review-and-recovery.md) for the full recovery contract.
    
    When the result is visible, review product fidelity against the source photo,
    background quality (clean edges, consistent
    lighting, natural shadow), color accuracy (do product colors match the
    original?), canvas fit, and the marketplace's current image guidance if
    applicable. Deliver the
    artifact links, observed dimensions, task ID, and
    `billing.net_charged_credits`. Offer at most one focused, unexecuted revision.
    Generated assets can also be viewed and managed at
    [beatra.ai](https://beatra.ai).
    
    If the tool returns insufficient credits: give the user the tool message in
    full. Do not change the URL. You may translate the other words. Do not replace
    it with your own top-up copy. Point the user to pay using that message.
    
    When the user asks how many credits remain, call `beatra.wallet.get`. When they
    ask what was charged, call `beatra.wallet.ledger`. Both are read-only. Do not
    invent an account-balance or top-up tool. Do not make `wallet.get` a required
    step before every paid submit.
    
    ## References by task
    
    - Choosing among clean background, lifestyle scene, and detail edit, or
      planning for a specific marketplace: [product routing](references/product-routing.md)
    - Turning a request into a scene specification anchored to the source product:
      [scene craft](references/scene-craft.md)
    - Exact request shapes, ordered-reference labeling, and JSON examples for each
      route: [workflow](references/workflow.md)
    - Lost task, slow task, cancellation, result review, or planning a revision:
      [review and recovery](references/review-and-recovery.md)
    - First install or expired authorization:
      [installation and authentication](references/installation-and-auth.md)
    - Bundled MCP Client commands and diagnostics:
      [Bundled MCP Client diagnostics](references/mcp-connection.md)
    - Installation registration: [installation registration](references/installation-registration.md)
    - Task lookup, polling, and result fields: [tasks and results](references/tasks-and-results.md)
    - Balance, validation, and structured errors:
      [billing, errors, and recovery](references/billing-errors-and-recovery.md)
    - Disconnecting the installation: [uninstall and disconnect](references/uninstall-and-disconnect.md)
    - Updates and security: [updates and security](references/updates-and-security.md)
    
    ## Installation and account operations
    
    For first use and shared operations, follow [installation and
    authentication](references/installation-and-auth.md), [installation
    registration](references/installation-registration.md), [tasks and
    results](references/tasks-and-results.md), [billing, errors, and
    recovery](references/billing-errors-and-recovery.md), and [uninstall and
    disconnect](references/uninstall-and-disconnect.md).
    
    ## Updates and security
    
    This Skill runs only the files installed with it and leaves them unchanged while
    it runs; it never downloads or executes other code. It talks to Beatra's official
    services over HTTPS, and a file you choose to upload goes only to the one-time
    upload address Beatra issues for it.
    
    Once a day at most, the bundled client reads Beatra's public version list. When
    the store or tool you installed from has a newer version, it prints one line with
    the command that updates it there. Updating stays your choice, through that store
    or tool. See [updates and security](references/updates-and-security.md).
    
  • skills/product-photo-studio/scripts/authorize.py61 KB72b2c4ca652dupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • skills/product-photo-studio/scripts/mcp_client.py86 KBce9e04eb42b3upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • skills/product-photo-studio/scripts/uninstall.py13 KB97f477fade16upstream ↗
    #!/usr/bin/env python3
    """Uninstall one Beatra Skill package without stranding the shared connection.
    
    The device connection (~/.beatra) is shared by every installed Beatra skill.
    Removing one skill must therefore never touch the shared credential while any
    other skill still uses it — even when the user asks for a complete removal.
    Only when this device's skill inventory says nothing else is left does the
    credential revoke itself and the local state leave with it.
    
    Every uncertain situation resolves to "keep the connection": a wrongly kept
    credential idles out server-side after 15 days and can be revoked from the
    Console at any time, while a wrongly revoked one breaks every remaining skill.
    
    This script never deletes the package directory it runs from — it reports the
    directory so the calling agent can remove it as the final step.
    """
    
    from __future__ import annotations
    
    import argparse
    import json
    import os
    import sys
    import urllib.error
    import urllib.request
    from pathlib import Path
    from typing import Any, Callable
    
    sys.dont_write_bytecode = True  # never leave __pycache__ inside the installed Skill
    
    REVOKE_PATH = "/oauth/device/revoke"
    PACKAGE_SLUG = "product-photo-studio"
    PACKAGE_DISPLAY_NAME = "AI Product Photo Studio"
    PACKAGE_VERSION = "0.3.1"
    HTTP_USER_AGENT = f"Beatra-Skill/{PACKAGE_SLUG}/{PACKAGE_VERSION}"
    
    #: Everything the connection owns inside ~/.beatra. Removal unlinks exactly
    #: these and the update records below, then removes each directory only if it
    #: is empty — the script never recursively deletes a directory it does not
    #: fully understand.
    _STATE_FILES = (
        "credentials.json",
        "installation.json",
        "host.json",
        "skills.json",
        "registrations.json",
        "network.json",
        "balance-holds.json",
        "pending_authorization.json",
        ".authorize.lock",
    )
    
    #: The per-install records in ~/.beatra/updates/<id>/: the version check,
    #: install source and registration, and a domestic channel's update lock. Only
    #: these names leave; an interrupted update's transaction stays, and so does
    #: every directory that still holds something.
    _UPDATE_RECORD_FILES = ("state.json", "source.json", "registration.json", "update.lock")
    _UPDATE_RECORD_DIGITS = frozenset("0123456789abcdef")
    
    PostRevoke = Callable[[str, str], int]
    
    
    class _RejectRedirectHandler(urllib.request.HTTPRedirectHandler):
        def redirect_request(
            self,
            _request: urllib.request.Request,
            _file_pointer: Any,
            code: int,
            _message: str,
            _headers: Any,
            _new_url: str,
        ) -> None:
            raise RuntimeError(f"Beatra revocation refused HTTP redirect ({code})")
    
    
    def _revoke_url(state_dir: Path) -> str:
        """Revoke over this computer's recorded network route. Uninstall never asks
        Beatra which route to use: revocation is best effort, and the record it
        would write is about to be removed."""
    
        import mcp_client  # the bundled client beside this file owns the route
    
        return f"{mcp_client.route_api_origin(mcp_client.current_route(state_dir))}{REVOKE_PATH}"
    
    
    def _default_post_revoke(token: str, url: str) -> int:
        request = urllib.request.Request(
            url,
            data=b"",
            headers={
                "Accept": "application/json",
                "Authorization": f"Bearer {token}",
                "User-Agent": HTTP_USER_AGENT,
            },
            method="POST",
        )
        opener = urllib.request.build_opener(_RejectRedirectHandler())
        try:
            with opener.open(request, timeout=30) as response:
                return int(response.status)
        except urllib.error.HTTPError as exc:
            return int(exc.code)
        except urllib.error.URLError as exc:
            raise RuntimeError("Beatra authorization service is unreachable") from exc
    
    
    def _own_skill_root() -> Path | None:
        """The installed package root this script runs from, when knowable."""
        try:
            return Path(__file__).resolve().parent.parent
        except NameError:
            return None
    
    
    def _load_inventory(state_dir: Path) -> tuple[list[dict[str, Any]] | None, str]:
        """Return (entries, state) where state names why entries may be None."""
    
        path = state_dir / "skills.json"
        if not path.exists():
            return None, "inventory_missing"
        try:
            value = json.loads(path.read_text(encoding="utf-8"))
        except (OSError, ValueError):
            return None, "inventory_unreadable"
        loaded = value.get("skills") if isinstance(value, dict) else None
        if not isinstance(loaded, list):
            return None, "inventory_unreadable"
        return [entry for entry in loaded if isinstance(entry, dict)], "ok"
    
    
    def _device_token(state_dir: Path) -> str | None:
        path = state_dir / "credentials.json"
        try:
            value = json.loads(path.read_text(encoding="utf-8"))
        except (OSError, ValueError):
            return None
        token = value.get("access_token") if isinstance(value, dict) else None
        if not isinstance(token, str) or not token:
            return None
        return token
    
    
    def _provably_deleted(install_path: str) -> bool:
        """True only when the entry's directory is verifiably gone.
    
        "Not currently visible" is not "uninstalled": an unmounted volume, a
        permission-restricted parent, or any stat error must count the skill as
        alive — the rule resolves every uncertainty to keeping the connection.
        Only a clean missing entry under a reachable parent is proof of removal.
        """
    
        try:
            os.stat(install_path)
            return False
        except FileNotFoundError:
            pass
        except OSError:
            return False
        parent = os.path.dirname(install_path.rstrip("/\\")) or os.sep
        try:
            os.stat(parent)
        except OSError:
            return False
        return True
    
    
    def _remove_update_records(state_dir: Path) -> None:
        updates = state_dir / "updates"
        try:
            if updates.is_symlink() or not updates.is_dir():
                return
            records = [
                child
                for child in updates.iterdir()
                if len(child.name) == 24
                and set(child.name) <= _UPDATE_RECORD_DIGITS
                and not child.is_symlink()
                and child.is_dir()
            ]
        except OSError:
            return
        for record in records:
            for name in _UPDATE_RECORD_FILES:
                try:
                    (record / name).unlink(missing_ok=True)
                except OSError:
                    pass
            try:
                record.rmdir()
            except OSError:
                pass
        try:
            updates.rmdir()
        except OSError:
            pass
    
    
    def _remove_local_state(state_dir: Path) -> None:
        for name in _STATE_FILES:
            try:
                (state_dir / name).unlink(missing_ok=True)
            except OSError:
                pass
        _remove_update_records(state_dir)
        try:
            state_dir.rmdir()
        except OSError:
            pass
    
    
    def uninstall(
        *,
        state_dir: Path | None = None,
        skill_root: Path | None = None,
        post_revoke: PostRevoke = _default_post_revoke,
        dry_run: bool = False,
    ) -> dict[str, Any]:
        """Apply the last-skill rule and return a machine-readable decision."""
    
        state_dir = (state_dir or Path.home() / ".beatra").expanduser()
        root = skill_root or _own_skill_root()
        resolved_root = str(Path(root).expanduser().resolve()) if root is not None else None
    
        result: dict[str, Any] = {
            "package": PACKAGE_SLUG,
            "dry_run": dry_run,
            "delete_next": resolved_root,
        }
    
        if not state_dir.is_dir():
            # Nothing local to retire; the package files are all that is left.
            result.update({"decision": "disconnected", "revoked": False, "reason": "no_local_state"})
            return result
    
        if resolved_root is None:
            result.update({"decision": "keep_connection", "reason": "unknown_install_path"})
            return result
    
        try:
            entries, inventory_state = _load_inventory(state_dir)
            if entries is None:
                # No trustworthy inventory means no proof this is the last skill —
                # the connection stays, and the 15-day idle expiry backstops it.
                result.update({"decision": "keep_connection", "reason": inventory_state})
                return result
    
            survivors: list[dict[str, Any]] = []
            for entry in entries:
                if (
                    entry.get("slug") == PACKAGE_SLUG
                    and entry.get("install_path") == resolved_root
                ):
                    continue  # the package being uninstalled
                install_path = entry.get("install_path")
                if isinstance(install_path, str) and install_path and _provably_deleted(install_path):
                    continue  # verifiably removed outside this flow — self-heal
                survivors.append(entry)
        except Exception:  # noqa: BLE001 -- any surprise resolves to keeping the
            # connection; a crash here must never read as permission to revoke.
            result.update({"decision": "keep_connection", "reason": "inventory_error"})
            return result
    
        if survivors:
            # The inventory is deliberately not rewritten here: this package's own
            # entry disappears through the provably-deleted check once the agent
            # finishes removing the directory, so an interrupted uninstall leaves
            # a still-installed skill visible rather than silently forgotten.
            result.update(
                {
                    "decision": "keep_connection",
                    "reason": "other_skills_still_installed",
                    "remaining_skills": [
                        {"slug": entry.get("slug"), "platform": entry.get("platform")}
                        for entry in survivors
                    ],
                }
            )
            return result
    
        if dry_run:
            result.update({"decision": "disconnected", "revoked": False, "reason": "dry_run"})
            return result
        token = _device_token(state_dir)
        revoked = False
        revoke_state = "no_credential"
        if token is not None:
            try:
                status = post_revoke(token, _revoke_url(state_dir))
                if status == 200:
                    revoked = True
                    revoke_state = "revoked"
                elif status == 401:
                    # The server no longer recognises this token; the local copy
                    # is worthless either way, but nothing was confirmed revoked.
                    revoke_state = "not_recognized"
                else:
                    # Reached but refused (rate limit, server error): keep every
                    # local file so a retry — or a Console revoke followed by a
                    # rerun — can still finish the job cleanly.
                    result.update(
                        {
                            "decision": "revoke_retry",
                            "revoked": False,
                            "reason": f"http_{status}",
                        }
                    )
                    return result
            except RuntimeError:
                revoke_state = "unreachable"
        token = None
        _remove_local_state(state_dir)
        result.update({"decision": "disconnected", "revoked": revoked, "reason": revoke_state})
        return result
    
    
    def main() -> int:
        parser = argparse.ArgumentParser(
            description=f"Uninstall the {PACKAGE_DISPLAY_NAME} Skill from this device"
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Report the decision without revoking or changing any file.",
        )
        args = parser.parse_args()
        result = uninstall(dry_run=args.dry_run)
        print(json.dumps(result, ensure_ascii=False, indent=2))
        if args.dry_run:
            print("Dry run: nothing was changed. Run without --dry-run to uninstall.")
            return 0
        if result["decision"] == "keep_connection":
            print(
                "The shared Beatra connection stays in place"
                + (
                    " because other installed skills still use it."
                    if result.get("reason") == "other_skills_still_installed"
                    else " because this device's skill inventory could not prove it is unused."
                )
            )
        elif result["decision"] == "revoke_retry":
            print(
                "Nothing was changed: the Beatra server refused the revocation just now. "
                "Run this script again shortly, or revoke the device in the Beatra "
                "Console and rerun to finish removing local state."
            )
            return 0
        elif result.get("revoked"):
            print("The Beatra device authorization is revoked and local state is removed.")
        else:
            print(
                "Local state is removed. The server-side authorization could not be "
                "confirmed revoked from here; it expires after 15 days of inactivity "
                "and can be revoked in the Beatra Console at any time."
            )
        if result.get("delete_next"):
            print(f"Finish by deleting the package directory: {result['delete_next']}")
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())
    
  • skills/product-photo-studio/references/billing-errors-and-recovery.md4.8 KB4492c70c65d8upstream ↗
    # Billing, errors, and recovery
    
    Every generation tool consumes Beatra credits and creates an asynchronous task.
    Use one logical request identity across transport uncertainty so recovery cannot
    accidentally create or charge a duplicate task.
    
    ## Submit a billable request
    
    1. Assemble the final generation arguments.
    2. Generate one opaque `client_request_id`.
    3. Submit exactly once.
    4. Store both `client_request_id` and the returned `task_id`.
    5. Poll with `beatra.tasks.get`; do not resubmit while the task is queued or
       running.
    
    The prepaid amount accepted with the request can be an estimate. A successful
    task is settled against authoritative measured usage. Read the terminal
    `usage` and `billing` fields, and report net credits as
    `billing.net_charged_credits`. Do not promise that the initial estimate
    is the final charge.
    
    ## Run a batch
    
    When one request becomes many paid calls (a list of items, a loop, or a
    script):
    
    1. Before the first paid call, price the whole batch from the live prices
       (`beatra.models.list`, or `beatra.social.tools.get` for social data) and
       tell the user the estimated total.
    2. Let the first `insufficient_balance` stop the whole batch. Do not go on to
       the next item; report which items finished and which did not.
    3. Resume only the unfinished items, and only after the user says they have
       topped up. A refused item keeps its `client_request_id` and arguments.
    
    ## Recover an uncertain response
    
    - If no response arrived, make an identical retry with the same
      `client_request_id` and exactly the same generation arguments.
    - If any prompt, input artifact, model, output setting, or other generation
      argument changes, treat it as new work and use a new `client_request_id`.
    - If the task ID was lost, use `beatra.tasks.list` before considering a retry.
    - If remote execution failed, report the terminal error. Start changed or newly
      requested work only with a new request ID.
    
    ## Handle structured failures
    
    - HTTP 401 authentication failure: do not replay the paid call automatically.
      Run `python3 scripts/authorize.py` and follow its output
      until it prints `Beatra is ready`, then recover with the original request
      identity and unchanged arguments.
    - Timeout, DNS/TLS, HTTP 429, HTTP 5xx, or another transport failure: preserve
      the credential. If delivery of a paid call is uncertain, retry only with the
      same `client_request_id` and exactly the same arguments after connectivity
      recovers.
    - Insufficient scope: explain that the active connection lacks required access;
      run `python3 scripts/authorize.py --force` only after the
      user explicitly chooses to reconnect the full Beatra authorization. Never ask
      for a media-specific grant.
    - Insufficient balance (error code `insufficient_balance`): relay the returned
      message to the user — it already carries the facts that matter: nothing was
      charged, the top-up link, credits take effect immediately, and the same
      request can be retried as-is. Translate into the user's language but keep
      the URL exactly as returned. Stop all remaining paid work, including the
      rest of a batch, and do not retry until the user says they have topped up.
      A successful task can also leave a negative balance when actual measured
      usage exceeded the estimate; the output remains valid, but new billable
      work must wait for a top-up.
    - After that refusal, `scripts/mcp_client.py` holds further calls to the same
      tool on this computer: it answers them with the same message and sends
      nothing (`details.local_hold`). Its `details.agent_note` names the balance
      check that lifts the hold once the user says they have topped up. If the user
      chose a cheaper request instead, run that cheaper call once with
      `--recheck-balance`. Never put `--recheck-balance` in a loop or script.
    - Idempotency conflict: the request identity was reused with different
      arguments. Do not retry it. Keep the original task if it exists, or use a new
      `client_request_id` only for user-approved changed work.
    - Rate or concurrency limit: honor the returned retry delay, then retry the
      exact same arguments with the same request identity. Do not create a
      replacement task.
    - Validation failure or model incompatibility: call `beatra.models.list` for current
      constraints, correct the invalid arguments, and use a new
      `client_request_id` because the generation inputs changed.
    - Upload expiration, MIME mismatch, or byte-length mismatch: request a fresh
      grant and upload the unchanged file again. Never reuse a consumed or expired
      grant.
    - Remote execution failure: preserve the Beatra error code and task ID;
      report only the failure classification and billing/refund fields returned by
      Beatra. Do not infer a different cause or promise a refund that is not shown.
    
    Never expose authentication tokens, complete private prompts, or sensitive
    input content while explaining recovery.
    
  • skills/product-photo-studio/references/installation-and-auth.md9.3 KB149c77b01f1aupstream ↗
    # Installation and authentication
    
    Use this reference when Beatra is newly installed, unavailable, or needs its
    authorization recovered. This flow is owned by the Skill and works the same way
    in any agent that can run Python 3.10 or newer and make HTTPS requests. The
    helpers use only the Python standard library; do not install packages for them.
    
    ## Keep one private local identity
    
    The Skill uses two user-only files:
    
    - `~/.beatra/installation.json` contains one stable, non-secret installation
      reference.
    - `~/.beatra/credentials.json` contains the single Device Token.
    
    On POSIX systems the directory must be mode `0700` and both files mode `0600`.
    On Windows the current user must be the only principal granted access through
    the file ACL. Never copy the Device Token into conversation context, stdout,
    stderr, a command argument, an environment variable, a log, a backup, a diff,
    or another file. A completed authorization leaves the token only in
    `~/.beatra/credentials.json`.
    
    ## Ensure Beatra is ready
    
    From the installed `beatra` Skill directory, run:
    
    ```text
    python3 scripts/authorize.py
    ```
    
    Use `python` instead when that is the local Python 3.10+ command. Always run this
    idempotent helper after installing or replacing a Beatra Skill and when
    recovering its connection. It validates the candidate credential with one
    non-billable live MCP request. A valid credential becomes Ready without opening
    the browser, so installing another Beatra package does not require a second
    approval.
    
    When no valid credential exists, or the live MCP endpoint definitively returns
    HTTP 401, the helper:
    
    1. reuses or creates `~/.beatra/installation.json`;
    2. continues an approval that is already open on this computer, or requests a
       new Device Authorization from Beatra. When the user already selected Allow
       on this computer and nothing collected it, Beatra connects at once — no
       second approval;
    3. prints the HTTPS approval link at once and opens it in the user's browser
       (only once for each approval). The URL fragment carries the approval code,
       so the page verifies the code by itself;
    4. waits while it checks every few seconds whether the user selected Allow,
       and keeps checking through brief network failures. One run waits up to
       about 15 minutes; an approval page stays open for up to an hour;
    5. atomically saves the returned Device Token to
       `~/.beatra/credentials.json` without printing an HTTP response body;
    6. validates the new credential with the same non-billable MCP request and
       prints `Beatra is ready` only after it succeeds.
    
    ### Wait for Allow
    
    The user usually needs a minute or more to sign in or create an account before
    selecting Allow. The helper waits for it: leave it running. If your host moves
    the command to the background, let it keep running and do not start a second
    one.
    
    Act on how each run ends:
    
    - `Beatra is ready` — the connection works; continue with the user's request.
    - `BEATRA_AUTH_PENDING` (exit status 3) — the run ended before the user selected
      Allow: the approval page had not been opened yet, the run's wait ran out, or
      the run was stopped. Show the approval link to the user as described below if
      you have not already, and again if the link is different. Run the same command
      at most twice in one turn; it continues the same approval, so the user
      approves only once. When it is still pending after that, deliver what needs
      no connection (below) and stop. When the message says Beatra could not be
      reached, tell the user the connection is being retried instead of asking them
      to approve again.
    - `BEATRA_AUTH_IN_PROGRESS` (exit status 3) — another run on this computer is
      starting an approval right now. Run the same command again in a moment; it
      continues that approval.
    - `Beatra authorization stopped (access_denied)` — the user declined on the
      approval page. A decline is often a mis-click: if the user still wants to
      connect, run the same command again and it opens a new approval page.
    - `Beatra authorization stopped (expired_token)` — the approval page expired
      before the user selected Allow. Tell the user; if they still want to
      connect, run the same command again and share the new approval link.
    
    A run stopped by a timeout or an interrupted tool call counts as one of the
    two runs in the turn: run the same command again once, since it continues the
    approval that is already open, and after a second stop deliver what needs no
    connection and stop.
    
    An approval does not depend on a run being active when the user selects Allow.
    Beatra keeps it, and every Beatra command collects it first: when the user
    writes again after approving, run the helper once (or go on with their
    request) and the connection completes without a second approval. So if the
    user interrupts to ask something else, stop the helper and answer them; and if
    they do not want to connect now, stop waiting — the link stays valid for a
    while.
    
    #### Deliver while the approval is pending
    
    When the approval is still pending at the end of a turn, finish and deliver
    everything in the request that needs no connection, in the format the user
    asked for — the Skill's root names it for each kind of work: the prepared
    text, prompt, lyrics, shot list, or selection criteria. Say in one line that
    the paid step continues on their next message after Allow, because the next
    Beatra command collects the approval. Do not fill the gap with another tool or
    service, and do not name voices, models, prices, or results that no live call
    returned.
    
    When telling the user what to do:
    
    - present the approval page as a link and say the only decision on it is
      selecting Allow;
    - never quote, display, or read out the approval code. It travels inside the
      link fragment, the approval page does not show it, and the user never types
      or compares it — a code surfaced in conversation is noise, not a step;
    - say prominently that a signed-out browser lands on Beatra sign-in first:
      the user signs in or creates their account there, and the approval page
      continues automatically after sign-in;
    - say once that the connection continues automatically after Allow — never
      ask the user to confirm in chat that they approved.
    - on the mainland China network, follow the sign-in and console-link guidance
      in [MCP connection](mcp-connection.md#network-route).
    
    The helper opens no local listener and requires no inbound connection. One
    approval covers image, video, music, speech, upload, model, and task tools.
    
    HTTP 403 or insufficient scope does not prove the credential is invalid. Keep
    the credential and run `python3 scripts/authorize.py --force`
    only when the user explicitly wants to reconnect or switch accounts. A timeout,
    DNS/TLS failure, HTTP 429, or HTTP 5xx is transient: keep the credential, do
    not start Device Authorization, and retry after connectivity recovers.
    
    ### Record where this Skill was installed from
    
    After this Skill first connects, if this conversation shows how this
    Skill was installed — for example an install command or link that names a
    store or site — record it once:
    
    ```text
    python3 scripts/mcp_client.py install-source --guess <source>
    ```
    
    Use one of `website`, `github`, `skillhub`, `skillatlas`, `modelscope`,
    `workbuddy`, `clawhub`, or `other`. If the conversation does not show it,
    skip this and do not ask the user. If the command is not recognized, skip it.
    
    ## Use the bundled MCP client
    
    Every Beatra Skill tool runs through the bundled `scripts/mcp_client.py`, which
    connects to `https://mcp.beatra.ai/mcp`. Do not add, enable, trust, or configure
    a host Beatra Connector. The bundled client reads the shared credential file
    and never accepts the token through argv or the environment:
    
    ```text
    python3 scripts/mcp_client.py verify
    python3 scripts/mcp_client.py tools
    python3 scripts/mcp_client.py call beatra.tasks.list
    ```
    
    The host follows this computer's network route; see [MCP connection](mcp-connection.md).
    
    For `call`, provide the tool arguments as one JSON object on stdin. Do not put
    user content or credentials in the command arguments. The authorization helper
    already performs this verification. Run the explicit `verify` command only for
    diagnostics; it invokes `beatra.tasks.list` with `{}` and is non-billable.
    
    The direct client follows the Streamable HTTP sequence:
    
    1. send `initialize` with the MCP protocol version and client information;
    2. retain an `MCP-Session-Id` response header when the server provides one;
    3. send the `notifications/initialized` notification;
    4. use `tools/list` for discovery and `tools/call` for execution;
    5. include the protocol version, optional session ID, and Device Token bearer
       header on every request.
    
    ## Recover or revoke access
    
    The single Device Token has a sliding 15-day idle lifetime. Every accepted MCP
    use extends that idle deadline without user action. If Beatra returns HTTP 401
    after the token was idle too long or was revoked, run
    `python3 scripts/authorize.py` and act on its output as
    described above; the helper replaces and validates the credential. Do not start another
    authorization for a media-type change and do not delete a credential because of
    a transient connection failure.
    
    The user can revoke a connected agent from the Beatra Console at any time.
    Revocation immediately invalidates the credential. Rerun Device Authorization
    only when the user wants to reconnect that installation.
    
    After first installing or replacing the Skill, start a new agent session when
    the host discovers Skills only at session startup.
    
  • skills/product-photo-studio/references/installation-registration.md2.0 KB7da7c1c9b57cupstream ↗
    # Installation registration
    
    On first use, and again when the version, agent environment, channel, network
    route, install source or installation reference changes or 24 hours pass, the
    bundled client makes one best-effort, non-billable
    `beatra.installations.register` call for this package. It is the only reporting call
    the Skill makes, it does not replace Device Authorization, it never asks for
    narrower package scopes, and its failure never blocks the user's requested
    task. It records:
    
    - the package, its version, the agent environment (`platform`), and the
      stable installation reference;
    - the distribution channel and locale fixed when the package was built;
    - the network route this computer uses and why (see the MCP connection
      reference);
    - where this copy was installed from, when known: a source file Beatra adds to
      copies it hands to a store or its website, a store client's own record in
      the Skill directory, or the agent's `install-source --guess`;
    - coarse runtime facts: operating system and version, processor architecture,
      Python version, interface language, and time-zone offset.
    
    It never sends user names, file paths, environment variables, prompts, or tool
    details. Beatra uses these fields to understand which stores and networks
    people install from, as its privacy policy describes, and clears the runtime
    facts when the account is deleted.
    
    A per-installation cache in `~/.beatra/updates/` suppresses another
    registration until the version, agent environment, channel, network route, install
    source or installation reference changes, or 24 hours pass. An unknown but well-formed package
    slug is accepted and recorded as unrecognized, so package release never
    depends on a synchronized backend registry deployment.
    
    The `platform` field is the real agent environment, resolved once per process
    from environment signatures (or `~/.beatra/host.json`, written at
    authorization time) — never detected per request. When authorizing manually,
    pass `--platform <environment-name>` to `scripts/authorize.py` if automatic
    detection cannot identify the host.
    
  • skills/product-photo-studio/references/mcp-connection.md3.3 KB9365c7be5051upstream ↗
    # MCP connection
    
    All Beatra Skill packages use one connection path:
    
    ```text
    scripts/mcp_client.py -> https://mcp.beatra.ai/mcp
    ```
    
    The host follows this computer's network route, described below.
    
    They share the one full-scope Device Token stored in
    `~/.beatra/credentials.json`. Installing or switching packages must not trigger
    another authorization when that credential is already valid. Do not add,
    enable, trust, or configure a host Beatra Connector. Never print or move the
    token into command arguments, environment variables, logs, chat, or another
    package directory.
    
    The bundled client performs MCP `initialize`, retains `MCP-Session-Id` when the
    server returns one, sends `notifications/initialized`, and then uses
    `tools/list` or `tools/call`. Its `verify` command runs the non-billable
    `beatra.tasks.list` operation. It supplies the private `Authorization: Bearer`
    header on every request without exposing its value.
    
    Source attribution: every `tools/call` may carry two optional string
    arguments, `source_package_slug` (this package's slug) and `source_platform`
    (the agent environment, for example `claude-code` or `codex`). The bundled
    client adds them automatically. They are telemetry only: missing values never
    block a call, and the server treats them as `unknown`.
    
    ## Network route
    
    Beatra serves two networks: the international hosts, and the mainland China
    hosts `mcp.beatra.cn`, `api.beatra.cn`, `www.beatra.cn`, `cdn.beatra.cn` and
    `console.beatra.cn`. The saved credential works on both. Packages of the
    mainland China (`cn`) channel always use the mainland hosts and cannot change
    network (`network` shows `"policy":"cn"`). Other packages choose once a day,
    before their first command: the client asks Beatra which network suits this computer,
    in a small request that carries no credential and no installation identity,
    and records the answer in `~/.beatra/network.json`. Approval pages, console and
    top-up links, uploads, and result downloads then follow the chosen network.
    
    ```text
    python3 scripts/mcp_client.py network
    python3 scripts/mcp_client.py network --use auto
    ```
    
    `network` shows the current choice. Only when the user asks for a specific
    network, run `network --use cn` (mainland China) or `network --use ai`
    (international): it first checks that Beatra answers on that network and
    refuses otherwise; packages of the `cn` channel refuse `--use`. If commands start failing to connect after a network was
    chosen this way, run `network --use auto`. A request that failed is never
    resent on the other network; the next command decides again unless a network was chosen with
    `network --use`. A chosen network also keeps the version-list check on it.
    
    On the mainland China network the approval page offers WeChat, phone and email
    sign-in. If the user cannot find their sign-in method, or wants to sign in with
    Google, run `python3 scripts/authorize.py --approval-link ai`: it shows the same
    approval on Beatra's international site, and the network does not change;
    `--approval-link auto` goes back. When you give the user a console or top-up
    link on the mainland network, remind them to sign in the way they did before
    (phone, email, or the same WeChat): scanning with a different WeChat creates a
    separate account. If credits do not appear after a top-up, first check that
    they signed in to the same account.
    
  • skills/product-photo-studio/references/product-routing.md3.9 KB9fd3e689139eupstream ↗
    # Product routing
    
    ## Start from the seller's actual starting point
    
    A product photo request arrives in one of three shapes. Identify which one
    before choosing a tool.
    
    | Starting point | What is enough to start | Route |
    | --- | --- | --- |
    | One product photo + background or scene goal | The product image plus one target look | `beatra.images.transform` |
    | One product photo + multiple style references | Ordered references (product first, style/background after) | `beatra.images.transform` |
    | Accepted draft + specific fix | The draft plus the requested change | `beatra.images.edit` |
    
    A single product photo with no explicit background preference is enough to
    propose a clean white background and include it in the single paid-call
    confirmation.
    
    ## Extract the product brief
    
    Collect what is already known and fill gaps only when they materially change
    the result.
    
    - **Product identity.** What the product is, its category, and any key visual
      details (brand label, shape, texture, color, packaging). Treat the source
      photo and confirmed details as the visual reference, then review the result
      for fidelity.
    - **Target marketplace.** Amazon, Taobao, Shopify, Rakuten, social media, or ad
      campaign—when it determines format or background rules.
    - **Background or scene direction.** Clean white, light-gray gradient, studio
      sweep, lifestyle context (kitchen, bathroom, office, outdoor), seasonal
      (holiday, summer).
    - **Visual references.** Style inspiration, desired background, angle
      reference, or a competitor listing to match—ordered with the product photo
      first.
    
    ## Marketplace image guidance
    
    Marketplace requirements change. When a named platform determines the output,
    ask for or consult its current image guidance before the paid call. Use these
    common patterns only as starting points:
    
    - **Amazon main image** — typically a pure-white background with the product
      prominent and without added text or watermarks.
    - **Taobao main image** — often a clean light background and square composition.
    - **Shopify product image** — consistent background across the catalog,
      square or 4:3 ratio preferred.
    - **Social media** — lifestyle or contextual scene with the product as hero.
    - **Ad campaign** — dramatic lighting, premium composition, may include text
      overlay (added downstream, not in the generated image).
    
    When the user names a marketplace, apply the current supplied guidance to the
    prompt. When absent, propose clean white as the default.
    
    ## Product category scene suggestions
    
    When the user requests a lifestyle scene but does not specify the setting,
    infer from the product category:
    
    - **Food and beverage** — wooden table, marble countertop, restaurant setting,
      natural light.
    - **Cosmetics and skincare** — bathroom shelf, vanity table, silk backdrop,
      soft diffused light.
    - **Electronics and tech** — desk surface, modern interior, dark dramatic
      background, cool blue accent light.
    - **Fashion and accessories** — model or mannequin context, fabric backdrop,
      neutral studio.
    - **Home and living** — styled room interior, bookshelf, windowsill, cozy
      warm light.
    - **Baby and kids** — nursery, soft pastel background, playful props.
    
    These are starting points, not constraints. Override with the user's stated
    preference whenever available.
    
    ## Defaults that avoid unnecessary questions
    
    - Canvas: square `1:1` for marketplace main images; `4:3` or `16:9` when the
      user specifies a banner or hero format.
    - Count: 1 (product photography requires precision, not exploration).
    - Model: `auto`.
    - Background: clean white when no preference is stated.
    
    ## Visual access
    
    Local files enter the workflow through `beatra.assets.upload`. Upload makes the
    bytes available to the remote tool; it does not itself inspect the image.
    Review only visual facts the host can actually see. When the host cannot view an
    image, state that visual verification was not possible and proceed on the
    user's declared intent.
    
  • skills/product-photo-studio/references/review-and-recovery.md3.7 KB41559738962cupstream ↗
    # Review and recovery
    
    ## Before the paid boundary
    
    Present one final direction. Freeze the prompt, ordered references, canvas,
    background or scene direction, count, model, and controls in a single
    confirmation. After approval, create one stable opaque `client_request_id` for
    that exact logical request and submit once. Any change—prompt, reference or
    order, canvas, scene direction, model, count, or control—is new paid work with
    a new confirmation and a new ID.
    
    ## Monitor and recover
    
    After receiving a `task_id`, poll only that task with `beatra.tasks.get`. Use
    bounded backoff: first poll around 2 seconds, increasing toward 5 and 10
    seconds, maximum 15 seconds. Honor `deadline_at`. Stop active polling after 30
    minutes and report the task ID and status.
    
    - **Lost create response:** if the response to the original submission is
      genuinely unknown, retry the exact same parameters with the same
      `client_request_id`.
    - **Lost task ID:** call `beatra.tasks.list` to find candidates, then verify
      the selected one with `beatra.tasks.get`. Match by capability, input, and
      timing. If the match is ambiguous, do not submit a replacement.
    - **Slow task:** a queued or running task never authorizes a replacement.
    - **Authorization failure:** run `python3 scripts/authorize.py` and follow its output until it prints `Beatra is ready`.
      The original `client_request_id` remains valid for idempotent recovery.
    - **Connection failure:** preserve the credential and the original
      `client_request_id`. Retry only the exact same parameters.
    
    Use `beatra.tasks.cancel` only when the user asks. If cancellation returns
    `409`, the task continues. See the shared
    [billing, errors, and recovery](billing-errors-and-recovery.md) for the full
    structured-failure reference.
    
    ## Review only what is visible
    
    When the result is visible, review in this order:
    
    1. **Product fidelity** — is the product itself recognizable, correctly shaped,
       and undistorted? Compare against the original product photo.
    2. **Color accuracy** — do the product's colors match the original? Look for
       color casts introduced by the scene lighting.
    3. **Edge quality** — are the product's edges clean where they meet the
       background? No halo, fringe, or ghosting.
    4. **Shadow and grounding** — does the product cast a natural contact shadow?
       A floating product looks artificial.
    5. **Lighting consistency** — does the light direction on the product match
       the scene's implied light source?
    6. **Marketplace guidance** — if targeting a specific marketplace, compare the
       image with the platform's current background, size, and framing guidance.
    
    If the host cannot view the artifact, state that visual verification was not
    possible and deliver the result links for the user to review.
    
    ## Deliver
    
    Present the returned image. Include the artifact links, observed dimensions,
    task ID, and `billing.net_charged_credits`. Do not infer credits from elapsed
    time.
    
    Offer at most one focused, unexecuted revision. When the user accepts a result
    but wants a specific fix, describe the revision as a new `beatra.images.edit`
    request with the accepted image as `images[0]`.
    
    ## Product image variant set
    
    A complete ecommerce listing typically uses several image variants:
    
    - **main image** — clean white background, product centered (marketplace
      requirement);
    - **lifestyle scene** — product in a real-world context;
    - **detail or infographic** — close-up of a feature or annotated callouts
      (text added downstream);
    - **scale or dimension** — product next to a common object for size reference;
    - **seasonal or campaign** — holiday or promotion-themed background.
    
    Each variant is a distinct paid request with its own confirmation and
    `client_request_id`. Review each result independently for product fidelity.
    
  • skills/product-photo-studio/references/scene-craft.md3.2 KB876e4f928e98upstream ↗
    # Scene craft
    
    ## Anchor the result to the product
    
    The product is the subject. Build the prompt around the source photo and
    confirmed product details, then verify fidelity in the result:
    
    - state explicitly that Image 1 is the product reference and ask the model to
      match its shape, color, label, and proportions closely;
    - describe only the background, lighting, and scene around it;
    - do not request changes to the product itself in a transform call—use
      `beatra.images.edit` for localized product fixes.
    
    ## Background quality
    
    A professional product background has three properties:
    
    - **clean edges** — the product's outline must have no halo, fringe, or
      ghosting where it meets the background;
    - **consistent lighting** — the light direction on the product must match the
      light direction implied by the background (shadows fall the same way);
    - **natural shadow** — the product should cast a subtle contact shadow on the
      surface beneath it; a floating product with no shadow looks artificial.
    
    Include these constraints in the prompt: "clean product edges, consistent
    lighting direction, natural contact shadow, no halo or fringe."
    
    ## Clean white background
    
    For marketplace main images requiring pure white:
    
    - state "pure white background, RGB 255 255 255" explicitly;
    - request even, shadowless lighting for the background while keeping a subtle
      contact shadow under the product for grounding;
    - avoid gradients, textures, or patterns unless the user explicitly requests
      them.
    
    ## Lifestyle scene composition
    
    For lifestyle and contextual scenes:
    
    - match the surface material to the product category (wood for food, marble
      for cosmetics, metal for tech);
    - keep the product as the focal point—larger and more prominent than any prop
      or background element;
    - use depth of field to separate the product from the background (sharp
      product, slightly soft background);
    - ensure color temperature consistency (warm scene = warm product lighting,
      cool scene = cool product lighting).
    
    ## Lighting direction
    
    State the light direction in the prompt to avoid conflicting shadows:
    
    - **front light** — even, flat illumination; good for catalog accuracy;
    - **side light** — dimensional, shows texture; good for food and crafts;
    - **backlight** — dramatic silhouette rim; good for glass and transparent
      products;
    - **top light** — natural overhead; good for flat-lay and top-down shots.
    
    The chosen direction must be consistent between the product lighting and the
    scene's implied light source.
    
    ## Color accuracy
    
    Use the original photo as the color reference, then review the result for color
    fidelity:
    
    - do not describe the product's color in the prompt (let the reference image
      determine it);
    - if the original photo has a color cast, note it and advise the user that
      color correction may be needed;
    - structured `palette` is optional—use it only when the user provides exact
      brand or background color values.
    
    ## Canvas and output
    
    - Default canvas for marketplace main images: square `1:1` at `2K` tier.
    - For banner or hero images: `16:9` or `4:3` at `2K`.
    - For edit: the default is `source`, which inherits the base image's ratio.
    - A different ratio is a separate paid request with its own confirmation and
      `client_request_id`.
    
  • skills/product-photo-studio/references/tasks-and-results.md2.2 KB34977869c667upstream ↗
    # Tasks and results
    
    Generation returns an asynchronous task. Keep its `task_id` and use
    `beatra.tasks.get` to poll it with bounded backoff: wait 2 seconds before the
    first poll, then increase the interval toward 5, 10, and at most 15 seconds.
    Honor `deadline_at` when the task response provides it. Otherwise stop active
    polling after 30 minutes, report the task ID and current status, and explain
    that the user or a later agent session can resume polling the same task.
    
    ## Interpret state
    
    - `queued` and `running` are nonterminal. Continue polling; do not submit a
      replacement generation.
    - `succeeded`, `failed`, and `canceled` are terminal. Stop polling.
    - On success, present returned artifacts, resolved model, actual usage, and
      billing truth. For prepaid work, report `billing.net_charged_credits`; the gross charged value
      may include a supplemental actual-usage debit and the refunded value may
      return an unused request-time estimate.
    - For a settled video task, `usage.video_seconds` is the authoritative
      billable total. When present, `usage.input_video_seconds` and
      `usage.output_video_seconds` are the authoritative components of that total.
      This distinction matters for reference-video generation and video editing,
      where both input and output can be billable. Do not infer either component
      when Beatra returns it as `null`.
    - On failure, preserve the structured error and apply the recovery reference.
    
    Use `beatra.tasks.list` to recover a lost task ID or inspect recent work from
    this connection. Filter only when doing so helps identify the task.
    
    Call `beatra.tasks.cancel` only when the user requests cancellation. Before
    remote execution starts, cancellation stops the task. After remote execution
    starts, Beatra returns `canceled` only when the stop is confirmed. A `409`
    means the task continues: do not promise a stop or refund, do not submit a
    replacement, and continue polling the same task.
    
    Do not infer an artifact URL, completion, usage, or charged credits from
    elapsed time. Return only fields present in Beatra's task response.
    For MCP task reads, prefer the direct `structuredContent.usage` and
    `structuredContent.billing` objects; the complete envelope remains available
    under `structuredContent.task`.
    
  • skills/product-photo-studio/references/uninstall-and-disconnect.md2.7 KB5435c8ecc181upstream ↗
    # Uninstall and disconnect
    
    Use this workflow when the user asks to remove, uninstall, or delete this
    package, or to disconnect this device from Beatra entirely.
    
    ## The one rule
    
    The device connection (`~/.beatra`) is shared by every installed Beatra skill.
    It may only be retired when no other Beatra skill remains on this device — and
    `scripts/uninstall.py` is the only component that can decide that. Never delete
    `~/.beatra` or any file inside it yourself, and never skip the script, even
    when the user asks for a complete removal: if another installed skill still
    uses the connection, the connection must survive this uninstall.
    
    ## Steps
    
    1. Run the bundled decision script first, while the package files still exist:
    
       ```
       python3 scripts/uninstall.py
       ```
    
       `--dry-run` prints the same JSON decision without changing anything or
       contacting the server. Never act on a dry-run report — rerun without the
       flag to actually uninstall.
    
    2. Read the JSON decision it prints:
    
       - `"decision": "keep_connection"` — another installed skill still uses the
         shared connection (`remaining_skills` lists them), or the inventory could
         not prove this is the last one. The credential and `~/.beatra` stay
         untouched. Tell the user which skills keep the connection alive.
       - `"decision": "disconnected"` — this was the last Beatra skill. The script
         has removed the local connection state. `"revoked": true` means the
         server confirmed the revocation; `"revoked": false` means it could not be
         confirmed from here (server unreachable, or the token was no longer
         recognised) — the authorization then expires on its own after 15 days of
         inactivity, and the user can revoke it immediately in the Beatra Console
         under Agents.
       - `"decision": "revoke_retry"` — the server was reached but refused just
         now (for example rate limiting). Nothing was changed. Run the script
         again shortly, or have the user revoke the device in the Beatra Console
         and then rerun it to finish removing local state.
    
    3. After a `keep_connection` or `disconnected` decision, finish by deleting
       only the package directory the script names in `delete_next`. Do not delete
       anything else. (After `keep_connection`, the inventory notices the deleted
       directory by itself — no further bookkeeping is needed.)
    
    ## Disconnecting everything
    
    There is no separate "disconnect all" action. Uninstall each installed Beatra
    skill with its own bundled script; the last one automatically revokes the
    authorization and clears `~/.beatra`. The user can always revoke the device
    from the Beatra Console as well — that invalidates the credential server-side
    immediately, whatever remains on disk.
    
  • skills/product-photo-studio/references/updates-and-security.md5.9 KB5c0172b54189upstream ↗
    # Updates and security
    
    Since the October 2026 releases, Beatra Skill packages contain no self-updater.
    Earlier releases could replace their own files from Beatra's CDN; that code has
    been removed. Updates now come only from the store or tool you installed the
    Skill from.
    
    ## What runs
    
    Every operation runs one of the bundled scripts: `scripts/mcp_client.py`,
    `scripts/authorize.py`, or `scripts/uninstall.py`. They use only the Python
    standard library. `scripts/authorize.py` opens the Beatra approval page in
    this computer's web browser when approval is still needed. They never
    download, write, or execute other code, and the files of this installed Skill
    stay exactly as installed.
    
    ## Where requests go
    
    Requests use HTTPS and follow this computer's network route, described in
    [MCP connection](mcp-connection.md): the international hosts, or the mainland
    China hosts `mcp.beatra.cn`, `api.beatra.cn`, and `www.beatra.cn`. When you
    choose to upload a local file, it goes only to the one-time upload address
    Beatra issues for that upload. Choosing the network may send a small request
    to the other network, and so may retrying the version list once, unless a
    network was chosen with `network --use`.
    
    ## What is stored on this computer
    
    These files are written under `~/.beatra/`. On POSIX that directory is mode
    0700. Nothing is written inside the installed Skill directory.
    
    - `credentials.json` (POSIX mode 0600), written by `scripts/authorize.py`: the device credential.
    - `installation.json` (POSIX mode 0600), written by `scripts/authorize.py`: the stable installation reference.
    - `host.json` (POSIX mode 0600), written by `scripts/authorize.py`: the agent environment, and a device name when one is known.
    - `network.json` (POSIX mode 0600), written by `scripts/mcp_client.py`: this computer's network route.
    - `skills.json` (POSIX mode 0600), written by `scripts/authorize.py` and `scripts/mcp_client.py`: which Beatra skills are installed here, with the slug, agent environment, install path, and time.
    - `pending_authorization.json` (POSIX mode 0600), written by `scripts/authorize.py`: an approval that is still open. Removed when that approval is collected or discarded.
    - `.authorize.lock` (POSIX mode 0600), written by `scripts/authorize.py` while authorization is in progress, then removed.
    - `balance-holds.json` (POSIX mode 0600), written by `scripts/mcp_client.py`: a local record after a tool is refused for insufficient credits. Removed when no such record remains.
    
    `scripts/mcp_client.py` also keeps one directory per installation under
    `~/.beatra/updates/`. Its name is the first 24 hexadecimal characters of the
    SHA-256 of the resolved installation path, and on POSIX the directory is mode
    0700. The files inside it are POSIX mode 0600:
    
    - `state.json`: the time of the last version check.
    - `registration.json`: the cache of the last installation registration.
    - `source.json`: where this copy was installed from, when that is known.
    
    Replacing one of these files writes a temporary file in the same directory and
    then renames it over the old file.
    
    When `scripts/uninstall.py` decides this is the last Beatra skill on the
    computer, it first asks Beatra to revoke the device credential when one is
    stored. If Beatra refuses, it changes nothing so the run can be repeated.
    Otherwise it unlinks exactly these names in `~/.beatra/`: `credentials.json`,
    `installation.json`, `host.json`, `skills.json`, `registrations.json`,
    `network.json`, `balance-holds.json`, `pending_authorization.json`, and
    `.authorize.lock`. It also unlinks `state.json`, `registration.json`, and
    `source.json` in each installation directory under `~/.beatra/updates/`. It
    then removes each of those directories, `~/.beatra/updates/`, and `~/.beatra/`
    only when that directory is empty. These
    scripts do not create `registrations.json`; uninstall still unlinks that name
    if it is present. The script never deletes the installed Skill directory. It
    names that directory in `delete_next` for the caller to delete. If another
    skill remains, or the inventory cannot prove this is the last one, it leaves
    `~/.beatra/` unchanged.
    
    ## What is reported
    
    Beyond the requests a command makes, the only report is the installation
    registration described in [installation registration](installation-registration.md).
    No prompts, file contents, file paths, or tool-call details are sent with it.
    Each tool call itself names this package and the agent environment (for example
    `claude-code`), as source attribution in [MCP connection](mcp-connection.md)
    describes, so a task can be traced to the Skill that made it.
    
    ## Version notice
    
    At most once every 24 hours, before an ordinary command, the bundled client
    reads Beatra's public version list for this package
    (`/skills/<package>/sources.json` on the site host of the current network). It
    works out from local records which store or tool installed this copy. When that
    place offers a newer version, it prints one line on standard error with the
    command that updates it there. It never installs anything. If the check fails,
    nothing is printed and the command runs as usual. A copy with no such record
    gets no notice.
    
    | Installed with | Update with |
    | --- | --- |
    | ClawHub CLI | `clawhub update <package> --workdir <dir> --dir <skills-dir>` (add `--force` if it reports local changes) |
    | SkillHub CLI | `skillhub install <package> --force --dir <skills-dir>` |
    | GitHub CLI | `gh skill update <package>`, run in the project directory for a project install |
    | skills CLI (GitHub or ModelScope) | `npx skills update <package> -p -y`, run in the project directory; or `npx skills update <package> -g -y` for a global install |
    | WorkBuddy | Update the Skill in WorkBuddy |
    | Beatra website (English packages) | Reinstall from the package's install page |
    
    ## Check by hand
    
        python3 scripts/mcp_client.py update --check
    
    It reports what the store or tool you installed from offers now. It installs
    nothing and leaves the Skill's files unchanged. If `python3` is not the local
    Python command, use the available Python 3.10+ command.
    
  • skills/product-photo-studio/references/workflow.md3.5 KB025f662a60b9upstream ↗
    # Workflow
    
    ## Build one product image brief
    
    Translate the product type, target marketplace, background or scene direction,
    and any style references into a single coherent prompt. Keep the prompt focused
    on the background and scene, not on changing the product.
    
    ## Prepare the selected route
    
    ### Transform to clean background
    
    Use `beatra.images.transform` with the product photo as the first ordered
    reference. Label the product's role explicitly.
    
    ```json
    {
      "prompt": "Professional ecommerce product photo. Image 1 is the product reference; match its shape, color, label, and proportions closely. Place it on a pure white background, with even studio lighting, a subtle contact shadow, and clean edges with no halo.",
      "model": "auto",
      "count": 1,
      "canvas": {
        "type": "preset",
        "tier": "2K",
        "aspect": "1:1"
      },
      "images": [
        {"type": "artifact", "artifact_id": "<product-photo-artifact-id>"}
      ],
      "client_request_id": "product-clean-bg-001"
    }
    ```
    
    ### Transform to lifestyle scene
    
    Use `beatra.images.transform` with the product photo first and optional style
    or background references after. Label each image's role.
    
    ```json
    {
      "prompt": "Professional lifestyle product photo. Image 1 is the product reference; match its shape, color, label, and proportions closely. Image 2 guides only the desired scene style and color palette. Place the product on a warm wooden kitchen counter, soft morning window light from the left, shallow depth of field, natural contact shadow.",
      "model": "auto",
      "count": 1,
      "canvas": {
        "type": "preset",
        "tier": "2K",
        "aspect": "1:1"
      },
      "images": [
        {"type": "artifact", "artifact_id": "<product-photo-artifact-id>"},
        {"type": "artifact", "artifact_id": "<scene-style-artifact-id>"}
      ],
      "client_request_id": "product-lifestyle-001"
    }
    ```
    
    When more than one reference is supplied, their order matters. The product
    photo should always be `images[0]`. Later images guide style, background, or
    angle only. The last image anchors the output ratio when canvas aspect is
    `source`.
    
    ### Edit an accepted draft
    
    Use `beatra.images.edit` with the accepted image as `images[0]`. Use at most
    two normalized `edit_regions` for targeted fixes; omit regions for a
    whole-image adjustment.
    
    ```json
    {
      "prompt": "Remove the small reflection on the lower left of the product bottle. Keep everything else unchanged.",
      "model": "auto",
      "count": 1,
      "images": [
        {"type": "artifact", "artifact_id": "<accepted-draft-artifact-id>"}
      ],
      "edit_regions": [
        {
          "image_index": 0,
          "x": 0.1,
          "y": 0.6,
          "width": 0.25,
          "height": 0.2
        }
      ],
      "client_request_id": "product-fix-reflection-001"
    }
    ```
    
    ## Apply model controls
    
    Keep `model=auto` unless the user explicitly requests a concrete model. Keep
    `count=1`—product photography requires precision, not variation. Call
    `beatra.models.list` with the relevant capability (`image_to_image` or
    `image_edit`) only when the user asks about model availability, compatibility,
    or price.
    
    ## Confirm, submit once, and monitor
    
    Present one final confirmation card containing the complete prompt, ordered
    references, canvas, background or scene direction, count, and model. After
    approval, create one stable opaque `client_request_id` and submit once. Record
    the returned `task_id` and poll with `beatra.tasks.get`.
    
    A changed prompt, reference set or order, canvas, scene direction, model,
    count, or control value is new paid work requiring a new confirmation and a new
    `client_request_id`.
    
  • skills/product-photo-studio/install-source.json39 B71bdc5669a3bupstream ↗
    {"schema_version":1,"source":"github"}
    
  • skills/product-photo-studio/manifest.json5.1 KB5d8fa80676b6upstream ↗
    {
      "changelog": "The helper shared by all Beatra Skills now runs its connection step at most twice in one turn. While your approval is still pending, the Skill delivers the part of your request that needs no connection and continues the paid step on your next message after Allow.",
      "display_name": "AI Product Photo Studio",
      "entry": "SKILL.md",
      "homepage": "https://beatra.ai/skills/product-photo-studio",
      "icon": "https://beatra.ai/textures/icons/product-photo-studio-0.2.9.png",
      "localized": {
        "en": {
          "description": "Transform a real product photo into a studio-quality ecommerce image, lifestyle scene, or marketplace-ready hero shot. This AI product photography tool replaces backgrounds, improves lighting, and stages scenes while using the source photo and confirmed product details as the visual anchor. Create clean white-background listings, contextual lifestyle compositions, and premium ad visuals from a single phone snap for Amazon, Taobao, Shopify, and social media. Start from one product photo, combine several references, or refine a selected draft toward a polished listing image.",
          "display_name": "AI Product Photo Studio",
          "summary": "Transform a real product photo into a studio-quality ecommerce image, lifestyle scene, or marketplace-ready hero shot with clean backgrounds, professional lighting, and composition guided by confirmed product details.",
          "tags": [
            "AI product photography",
            "product photo editor",
            "product image background",
            "ecommerce product photo",
            "product photo background remover",
            "AI product shoot",
            "white background product photo",
            "lifestyle product photography",
            "product hero image",
            "amazon product image",
            "product photo studio",
            "product listing photo"
          ],
          "title": "AI Product Photo Studio: Ecommerce Product Photography & Background Remover"
        },
        "ja": {
          "description": "実際の製品写真をスタジオ品質のEC画像、ライフスタイルシーン、または販売プラットフォーム向けのヒーローショットに変換します。このAI商品写真ツールは、元写真と確認済みの商品外観を基準に、背景の置換、照明の改善、シーンの演出を行います。スマホで撮った1枚から、白背景のメイン画像、ライフスタイルシーン、上質な広告ビジュアルを作成し、Amazon、楽天、Shopify、SNS向けの商品画像に活用できます。1枚の製品写真、複数の参考画像、または選んだドラフトから、目的に合う商品画像へ仕上げます。",
          "display_name": "AI 商品写真スタジオ",
          "summary": "実際の製品写真をスタジオ品質のEC画像、ライフスタイルシーン、または販売プラットフォーム向けのヒーローショットに変換。背景置換、照明改善、シーン演出に対応し、確認済みの商品外観を基準に構成します。",
          "tags": [
            "AI商品写真",
            "商品画像作成",
            "背景切り抜き",
            "製品画像編集",
            "商品写真 白背景",
            "楽天 商品画像",
            "EC用画像作成",
            "商品写真 レタッチ",
            "メルカリ 写真",
            "商品ライフスタイル写真",
            "製品ヒーロー画像",
            "商品画像 背景"
          ],
          "title": "AI 商品写真スタジオ:EC製品画像作成と背景切り抜き"
        },
        "zh": {
          "description": "将真实产品照片制作成影棚级电商主图、场景图或面向市场平台的高清商品图。这款 AI 商品图工作室用于商品图片处理和电商图片编辑,可替换背景、改善光效、搭建场景。它以原图中的商品外观特征为依据,从一张手机随拍就能做出白底主图、生活场景图和高级广告图。适用于亚马逊白底产品图、淘宝主图、Shopify 产品大图和社交媒体商品图。从一张产品照片出发,组合多张参考图,或在选定草稿上做定向精修,完成适合上架使用的成品;生活场景图和广告图也能直接用作商品详情页图片。",
          "display_name": "商品图",
          "summary": "将真实产品照片转化为影棚级电商主图、场景图或面向市场平台的高清商品图,支持换背景、改善光效和搭建场景,并延续已确认的商品外观特征。",
          "tags": [
            "AI商品图",
            "产品图制作",
            "商品图片处理",
            "电商产品图",
            "商品白底图",
            "产品主图",
            "淘宝主图制作",
            "商品场景图",
            "产品精修图",
            "商品详情页图片",
            "电商图片编辑",
            "产品拍照",
            "产品图"
          ],
          "title": "产品图工作室:AI 商品图、电商主图与背景替换"
        }
      },
      "mcp": {
        "authentication": "device-bearer",
        "credential_file": "~/.beatra/credentials.json",
        "name": "beatra",
        "transport": "streamable-http",
        "url": "https://mcp.beatra.ai/mcp"
      },
      "name": "product-photo-studio",
      "schema_version": 1,
      "version": "0.3.1"
    }
    
  • README.mdupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

Plain-text copy, no login: /skill-bundles/beatra-ai-ai-product-photography-skill-skills-product-photo-studio.md

// REPO STATS

7 stars