Metadata-Version: 2.4
Name: sellerclaw-cli
Version: 0.18.0
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.

The CLI ships a hand-curated set of typed command groups over the SellerClaw Agent API, with built-in discovery (`groups` / `commands` / `describe` / `guide`) so an agent can explore the surface without external docs. When no curated command fits a Shopify task, a raw Admin GraphQL query/mutation is reachable via `sellerclaw shopify graphql`.

---

## 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)
  - [Command groups](#command-groups)
  - [Discovery](#discovery)
  - [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)
- [Agent identification](#agent-identification)
- [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 shopify-listings list <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 shopify-listings list "$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
```

### Command groups

The CLI is organized into hand-curated top-level groups (`channels`, `shopify-listings`, `ebay-listings`, `research-seo`, `orders`, …), each with a small set of consistent verbs (`list`, `get`, `create`, `update`, `delete`, plus domain verbs like `publish`, `sync`, `search`). Path / parent ids are positional in path order; filters are flags:

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

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

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

Use `--help` on any group or command to see its exact arguments.

### Discovery

The discovery commands are driven by the live command registry (no external docs needed):

```sh
# One-shot onboarding: conventions, group list, how to call commands
sellerclaw guide

# Every group with a one-line summary and command count
sellerclaw groups

# Commands in a group (omit --group for all)
sellerclaw commands --group shopify-listings

# Full detail for one command: positionals, flags, body, example
sellerclaw describe shopify-listings create
```

When no curated command fits a Shopify task, run a raw Admin GraphQL operation with
`sellerclaw shopify graphql <STORE_ID> -b '{"query": "...", "variables": {...}}'`.

### Passing request bodies

Any command that takes a request body accepts `--body` (short: `-b`; `--json-body` is kept as a deprecated alias). It supports three sources:

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

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

# 3. Stdin (use @- as the value)
cat product.json | sellerclaw shopify-listings create <STORE_ID> \
  --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 group/command |
| `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 shopify-listings list "$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
```

---

## Agent identification

When the CLI is invoked from inside an LLM agent's workspace directory, it automatically attaches an `X-Agent-Id` header to every request so the server can attribute the call to the right agent. No flag, no env var, no caller-side wiring — the identifier is derived purely from the current working directory.

The CLI looks for a `workspace-<id>` segment in `os.getcwd()` and uses the **last** match. The id must match `^[A-Za-z0-9_-]+$` and be 1–64 characters; otherwise it is dropped silently and the header is not sent.

Example:

```text
cwd /home/node/.openclaw/workspace-supervisor
  → X-Agent-Id: supervisor

cwd /home/node/.openclaw/workspace-product_scout/sub/dir
  → X-Agent-Id: product_scout

cwd /home/node                       (no workspace segment)
  → header omitted
```

The header is purely informational on the server side; requests without it work exactly the same. There is no override knob — by design, the id is whatever the cwd says it is.

---

## 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 group: …"}}` / `unknown command …`**
The group or command name doesn't exist in your installed version. Run `sellerclaw groups`, then `sellerclaw commands --group <group>` to see what's available; upgrade the package if the endpoint is newer.

**`{"error":{"code":"user_error",…}}` on a missing positional argument**
A required path / parent id wasn't supplied. Run `sellerclaw describe <group> <command>` to see the positionals (in path order) and an example invocation.

**`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. Command groups are **hand-curated** — see [`sellerclaw_cli/_command_group.py`](sellerclaw_cli/_command_group.py) (declarative `Cmd` specs) and the modules under [`sellerclaw_cli/commands/`](sellerclaw_cli/commands/). Discovery (`groups` / `commands` / `describe` / `guide`) is driven by the populated command registry, not an OpenAPI snapshot. When the API gains an endpoint, add or update the relevant group module.

From the repo root:

```sh
make cli-lint    # ruff + pyright
make cli-test    # unit tests (pytest + respx)
make cli-build   # build wheel + sdist
```

Releases are triggered by pushing a `cli-X.Y.Z` tag.
