Metadata-Version: 2.4
Name: sellerclaw-cli
Version: 0.1.1
Summary: Command-line client for the SellerClaw Agent API — LLM-friendly JSON output, device-flow auth, auto-generated from OpenAPI.
Project-URL: Homepage, https://sellerclaw.com
Project-URL: Repository, https://github.com/sellerclaw/sellerclaw
Project-URL: Issues, https://github.com/sellerclaw/sellerclaw/issues
Author: SellerClaw
License: MIT
Keywords: agent,cli,ecommerce,llm,sellerclaw
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: attrs>=24.2.0
Requires-Dist: httpx>=0.28.0
Requires-Dist: pydantic>=2.11.0
Requires-Dist: python-dateutil>=2.9.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: tomli-w>=1.0.0
Requires-Dist: typer>=0.15.0
Provides-Extra: mcp
Description-Content-Type: text/markdown

# sellerclaw-cli

Command-line client for the [SellerClaw](https://sellerclaw.com) Agent API.

`sellerclaw-cli` is designed to be driven either directly from a terminal or, more commonly, as a subprocess by automation and LLM agents. Every command:

- returns structured **JSON on stdout** on success,
- returns structured **JSON on stderr** on failure,
- exits with a **stable, categorical exit code**,
- reads credentials only from env / config file — **never** from argv.

Every documented endpoint of the SellerClaw Agent API is reachable: the CLI's typed subcommands are generated from the API's OpenAPI schema, so there are no hand-written wrappers that can fall behind.

---

## Table of contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Configuration](#configuration)
  - [Environment variables](#environment-variables)
  - [Config file](#config-file)
  - [Priority / resolution order](#priority--resolution-order)
  - [Inspecting the current config](#inspecting-the-current-config)
- [Authentication](#authentication)
  - [Device flow (recommended)](#device-flow-recommended)
  - [Email + password](#email--password)
  - [Manual token](#manual-token)
  - [Logging out](#logging-out)
- [Using the CLI](#using-the-cli)
  - [Command structure](#command-structure)
  - [Typed commands (per tag)](#typed-commands-per-tag)
  - [Generic `call` / `list-operations`](#generic-call--list-operations)
  - [Passing request bodies](#passing-request-bodies)
  - [Output formats](#output-formats)
- [Output contract](#output-contract)
  - [Success shape](#success-shape)
  - [Error shape](#error-shape)
  - [Exit codes](#exit-codes)
  - [Retry behavior](#retry-behavior)
- [Using from scripts and LLM agents](#using-from-scripts-and-llm-agents)
- [Environment variables reference](#environment-variables-reference)
- [Troubleshooting](#troubleshooting)
- [Uninstall](#uninstall)
- [Development](#development)

---

## Requirements

- Python **3.11+** (3.11, 3.12, 3.13 are all tested in CI).
- Linux, macOS, or Windows (POSIX file-permission handling applies only on Linux/macOS).
- Network access to your SellerClaw Agent API endpoint.

## Installation

Install from PyPI:

```sh
pip install sellerclaw-cli
```

or with [`uv`](https://docs.astral.sh/uv/):

```sh
uv pip install sellerclaw-cli
# or, to install as a tool you can run anywhere:
uv tool install sellerclaw-cli
```

Verify the install:

```sh
sellerclaw --version
# 0.1.0
```

The installed binary is `sellerclaw` (not `sellerclaw-cli`).

## Quick start

```sh
# 1. Install
pip install sellerclaw-cli

# 2. Authenticate (opens a URL + code you confirm in a browser)
sellerclaw auth login

# 3. Call an endpoint
sellerclaw stores list-listings <STORE_ID> --status active --limit 5
```

That's it. See below for details on configuration, other auth flows, and output conventions.

---

## Configuration

The CLI needs two pieces of configuration:

- **`api_url`** — base URL of the Agent API (default: `https://api.sellerclaw.com`).
- **`token`** — your personal `sca_…` agent token used for `Authorization: Bearer …` on every request.

Both can be supplied either via environment variables or via a config file.

### Environment variables

| Variable | Purpose | Default |
| --- | --- | --- |
| `SELLERCLAW_TOKEN` | Agent token (`sca_…`). Always sent as `Authorization: Bearer <token>`. | *(none)* |
| `SELLERCLAW_API_URL` | Base URL of the Agent API. | `https://api.sellerclaw.com` |
| `XDG_CONFIG_HOME` | Base dir for the config file (standard XDG behavior). | `~/.config` |

Example:

```sh
export SELLERCLAW_TOKEN="sca_abc123..."
export SELLERCLAW_API_URL="https://api.example.com"
sellerclaw auth whoami
```

### Config file

The config file is a small TOML file stored at:

- `$XDG_CONFIG_HOME/sellerclaw/config.toml` if `XDG_CONFIG_HOME` is set, otherwise
- `~/.config/sellerclaw/config.toml`.

Format:

```toml
api_url = "https://api.example.com"
token = "sca_abc123..."
```

Either key is optional. The file is created automatically by `sellerclaw auth login` / `sellerclaw auth logout`, with mode `0600` (owner read/write only) on POSIX systems.

You can also create or edit the file by hand:

```sh
mkdir -p ~/.config/sellerclaw
cat > ~/.config/sellerclaw/config.toml <<'EOF'
api_url = "https://api.example.com"
token   = "sca_abc123..."
EOF
chmod 600 ~/.config/sellerclaw/config.toml
```

### Priority / resolution order

For each of `api_url` and `token`, the CLI picks the **first** non-empty source, resolved independently:

1. Environment variable (`SELLERCLAW_API_URL`, `SELLERCLAW_TOKEN`).
2. Key in `config.toml` (`api_url`, `token`).
3. Built-in default (`api_url = https://api.sellerclaw.com`; no default token).

This means you can, for example, keep `api_url` in the config file and inject the token only via env — handy for CI and container workflows:

```sh
# ~/.config/sellerclaw/config.toml has only api_url
SELLERCLAW_TOKEN="$CI_TOKEN" sellerclaw stores list-listings "$STORE_ID"
```

### Inspecting the current config

```sh
sellerclaw auth whoami
# {"data":{"authenticated":true,"api_url":"https://api.example.com","config_path":"/home/you/.config/sellerclaw/config.toml"}}
```

`authenticated: true` means a token is configured (it is not validated against the server — make any real call to verify).

---

## Authentication

### Device flow (recommended)

Best for interactive use on your own machine:

```sh
sellerclaw auth login
```

The CLI prints a URL and a short code **to stderr** (so it never pollutes a stdout pipeline):

```
Open https://sellerclaw.com/activate
Enter the code: ABCD-1234
(waiting up to 600s, polling every 5s...)
```

Open the URL in a browser, paste the code, confirm. The CLI polls the API until the token is granted, then writes it to the config file. On success, stdout gets:

```json
{"data":{"authenticated":true,"api_url":"https://api.sellerclaw.com","config_path":"/home/you/.config/sellerclaw/config.toml"}}
```

### Email + password

```sh
sellerclaw auth login --password
# Email: you@example.com
# Password: ********
```

When stdin is not a TTY (e.g. piped), supply two lines — email, then password:

```sh
printf '%s\n%s\n' "you@example.com" "$PASSWORD" \
  | sellerclaw auth login --password
```

Password is never echoed and never appears in argv or environment.

### Manual token

If you already have an `sca_…` token (for example, provisioned by a backoffice tool), just put it into the config file or export the env var:

```sh
export SELLERCLAW_TOKEN="sca_abc123..."
# OR
echo 'token = "sca_abc123..."' >> ~/.config/sellerclaw/config.toml
```

### Logging out

```sh
sellerclaw auth logout
```

Removes the `token` key from the config file. The `api_url` setting (and anything else in the file) is preserved.

---

## Using the CLI

### Command structure

```
sellerclaw [GLOBAL OPTIONS] <group> <command> [COMMAND ARGS]
```

Discover what's available:

```sh
sellerclaw --help                        # list top-level groups
sellerclaw <group> --help                # list commands in a group
sellerclaw <group> <command> --help      # show args + options for one command
```

### Typed commands (per tag)

Each OpenAPI **tag** becomes a top-level group (`stores`, `ebay-listings`, `research-seo`, `agent-goals`, …), and each **operation** within that tag becomes a subcommand. Path parameters are positional, query parameters are flags:

```sh
# GET /agent/stores/{store_id}/listings?status=...&limit=...
sellerclaw stores list-listings <STORE_ID> --status active --limit 10

# GET /agent/ebay/listings/{listing_id}
sellerclaw ebay-listings get <LISTING_ID>

# POST /agent/research/seo/keyword-ideas
sellerclaw research-seo keyword-ideas --seed widgets
```

Use `--help` on any command to see its exact arguments — the help text comes straight from the OpenAPI summary and schema.

### Generic `call` / `list-operations`

If you know the OpenAPI `operationId` directly, or you're hitting an endpoint that hasn't been promoted to a typed subcommand, use the generic fallback:

```sh
# List every operation in the bundled OpenAPI snapshot
sellerclaw list-operations

# Filter by tag
sellerclaw list-operations --tag stores

# Invoke by operation_id, supply path/query/body manually
sellerclaw call list_listings \
  --path-param store_id=<STORE_ID> \
  --query-param status=active \
  --query-param limit=10
```

`--path-param` and `--query-param` are repeatable; both use `KEY=VALUE` syntax.

### Passing request bodies

Any command whose OpenAPI operation has a `requestBody` accepts `--json-body` (short: `-b`). It supports three sources:

```sh
# 1. Literal JSON on the command line
sellerclaw stores publish-shopify-products <STORE_ID> \
  --json-body '{"ids": ["l1", "l2"]}'

# 2. File
sellerclaw stores create-shopify-products <STORE_ID> \
  --json-body @./product.json

# 3. Stdin (use @- as the value)
cat product.json | sellerclaw stores create-shopify-products <STORE_ID> \
  --json-body @-
```

The body is validated as JSON locally before any network call; invalid JSON exits with a `user_error` (exit 1).

### Output formats

`--format` is a **global** option (before the subcommand), defaulting to `json`:

| Value | Description |
| --- | --- |
| `json` (default) | Compact, single-line JSON. Ideal for pipelines (`jq`, LLM tools, scripts). |
| `pretty` | Indented JSON (2 spaces). Still valid JSON. |
| `yaml` | YAML. |
| `table` | ASCII table when the payload is a list of flat dicts; falls back to pretty JSON otherwise. |

```sh
sellerclaw --format pretty stores list-listings <STORE_ID>
sellerclaw --format yaml   stores list-listings <STORE_ID>
sellerclaw --format table  stores list-listings <STORE_ID>
```

**Errors are always compact JSON on stderr**, regardless of `--format`. Downstream error parsers can rely on a single stable shape.

---

## Output contract

### Success shape

On exit code `0`, stdout contains exactly one JSON value (plus a trailing newline):

```json
{"data": <payload>}
```

`<payload>` is whatever the API returned for that endpoint — a single object, a list, a primitive, or `null`.

### Error shape

On any non-zero exit, stderr contains exactly one JSON value:

```json
{
  "error": {
    "code":    "auth_error",
    "message": "Unauthorized",
    "status":  401,
    "details": { /* optional, parsed from the API response body */ },
    "hint":    "Run `sellerclaw auth login` to authenticate."
  }
}
```

Field presence:

- `code` and `message` — always present.
- `status` — present when the error originated from an HTTP response.
- `details` — present when the API returned a structured error body.
- `hint` — present for `auth_error` (currently always points at `sellerclaw auth login`).

### Exit codes

| Code | Meaning | Typical triggers |
| --- | --- | --- |
| `0` | Success | any 2xx |
| `1` | User error | invalid CLI args, invalid JSON body, 4xx (non-auth), unknown `operation_id` |
| `2` | Server / network error | 5xx after retries, connection refused, timeout |
| `3` | Auth error | 401, 403, missing token |

These are stable and categorical — scripts can switch on them without parsing stderr.

### Retry behavior

The CLI transparently retries up to **3 attempts** on these conditions:

- HTTP status `429`, `502`, `503`, `504`
- Transient transport errors: `ConnectError`, `ReadError`, `WriteError`, timeouts

Backoff between retries is exponential with a small jitter, capped at 10 seconds. When the server sends a `Retry-After` header (seconds), the CLI honors it exactly (no extra jitter on top). All other statuses and unknown httpx errors are returned immediately — no retries.

Requests time out after **30 seconds** by default.

---

## Using from scripts and LLM agents

The CLI was designed specifically to be wrapped as a subprocess. Conventions that make that robust:

- **Pure JSON on stdout.** No progress bars, no log lines, no `rich` decoration leaking from stdout. Anything human-facing (login prompts, device-flow URL) is on stderr only.
- **Pure JSON on stderr** on failure, with a stable shape.
- **Categorical exit codes.** You can branch on `returncode` without touching stderr.
- **No credentials in argv.** Even if you see a new CLI flag in the future, credentials will never be one of them. Pass them via env.
- **No interactive prompts in non-TTY mode.** `auth login --password` reads from piped stdin; every other command just runs.

Minimal Python wrapper:

```python
import json, os, subprocess

def run(*args, token=None, timeout=60):
    env = {**os.environ}
    if token:
        env["SELLERCLAW_TOKEN"] = token
    r = subprocess.run(
        ["sellerclaw", *args],
        env=env,
        capture_output=True,
        text=True,
        timeout=timeout,
    )
    if r.returncode == 0:
        return json.loads(r.stdout)["data"]

    err = json.loads(r.stderr)["error"]
    if r.returncode == 3:
        raise AuthError(err["message"])            # refresh token, retry
    if r.returncode == 2:
        raise TransientError(err["message"])       # safe to retry with backoff
    raise UserError(err["message"], details=err.get("details"))

stores = run("stores", "list-listings", store_id, "--status", "active", token=my_token)
```

Minimal shell wrapper:

```sh
if out=$(sellerclaw stores list-listings "$STORE_ID" --status active 2>err.json); then
    echo "$out" | jq '.data'
else
    code=$?
    jq '.error' err.json
    case $code in
        3) echo "auth failed — refresh token" ;;
        2) echo "transient — retry later" ;;
        *) echo "fatal" ;;
    esac
    exit $code
fi
```

---

## Environment variables reference

| Variable | Read by | Purpose |
| --- | --- | --- |
| `SELLERCLAW_TOKEN` | every command | Agent token, sent as `Authorization: Bearer <token>`. Highest priority. |
| `SELLERCLAW_API_URL` | every command | Base URL of the Agent API. Overrides config file and default. |
| `XDG_CONFIG_HOME` | `auth *`, `whoami` | Base dir for the config file. Defaults to `~/.config`. |

---

## Troubleshooting

**`{"error":{"code":"auth_error",…,"status":401,…}}` / exit 3**
No token found, or token is wrong / expired. Run `sellerclaw auth login`, or check `sellerclaw auth whoami` to see which sources are in play and what `api_url` the CLI is targeting.

**`{"error":{"code":"network_error",…}}` / exit 2**
Can't reach the API. Verify `SELLERCLAW_API_URL` / config `api_url`, DNS, firewall. The CLI already retries transient failures 3× with backoff — a persistent `network_error` usually indicates a misconfigured URL or blocked egress.

**`{"error":{"code":"server_error",…,"status":5xx}}` / exit 2**
API itself returned 5xx after the CLI retried. If it persists, the platform is having an incident; otherwise retry later.

**`{"error":{"code":"user_error","message":"unknown operation_id: …"}}`**
The generic `sellerclaw call <id>` can't find that `operation_id` in the bundled OpenAPI snapshot. Use `sellerclaw list-operations` to see what's available in your installed version; upgrade the package if the endpoint is newer.

**`{"error":{"code":"user_error","message":"missing required path parameter(s): …"}}`**
You invoked `sellerclaw call` without supplying all `--path-param KEY=VALUE` entries required by the URL template.

**`sellerclaw: command not found` after `pip install`**
The install directory isn't on `PATH`. On many systems `pip install --user` installs into `~/.local/bin`; add that to `PATH` or use `pipx install sellerclaw-cli` / `uv tool install sellerclaw-cli` which handle it for you.

**My config file keeps getting ignored**
Check `sellerclaw auth whoami` — the `config_path` it prints is the one it reads. Common causes: `XDG_CONFIG_HOME` pointing somewhere unexpected, or a typo in the TOML (it must be `api_url` and `token`, lowercase, at the top level).

---

## Uninstall

```sh
pip uninstall sellerclaw-cli
# optional — remove config + token
rm -rf ~/.config/sellerclaw
```

---

## Development

This package lives in [`packages/sellerclaw-cli/`](.) of the main SellerClaw monorepo. Commands and the OpenAPI snapshot bundled into the package are **generated** from the backend's `openapi/agent.json`; do not hand-edit files under `sellerclaw_cli/commands/` or `sellerclaw_cli/_spec.json`.

From the repo root:

```sh
make cli-sync    # regenerate commands/*.py + _spec.json from openapi/agent.json
make cli-lint    # ruff + pyright
make cli-test    # unit tests (pytest + respx)
make cli-build   # build wheel + sdist
```

CI enforces zero drift between `openapi/agent.json` and the generated files. Releases are triggered by pushing a `cli-X.Y.Z` tag.
