Metadata-Version: 2.5
Name: kurumera
Version: 0.1.0
Summary: Drive a Kurumera store from Python - the platform's tools, the platform's permissions.
Project-URL: Homepage, https://kurumera.com
Project-URL: Documentation, https://kurumera.com
Author: Kurumera
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ecommerce,kurumera,mcp,storefront
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# kurumera

Drive a Kurumera store from Python — the platform's tools, the platform's permissions.

```bash
pip install kurumera
```

```python
from kurumera import Kurumera

km = Kurumera()                                  # key from KURUMERA_API_KEY

km.create_product(title="Badam 500g", product_type="Dry Fruit")
for product in km.paginate(km.list_products, limit=100):
    print(product["title"])
```

Every method is one of the platform's tools, under the same name it has everywhere else.
Anything you already know about `create_product` or `apply_page_edits` is still true here.

---

## The store comes from your key

There is no tenant, store or shop argument. Not in the constructor, not per call, not as an
environment variable. The server works out which store you mean from your credential and
ignores anything a client claims.

That is a security property, not an omission: a client that could name a store is a client a
prompt-injected agent could aim at someone else's data by inventing an id. This package
refuses tenant-shaped arguments even on the dynamic `call()` path.

## Credentials

First one found wins:

1. `Kurumera(api_key="tps_…")`
2. `KURUMERA_API_KEY` in the environment
3. `~/.kurumera/config.json` — written by `kurumera login`

```bash
export KURUMERA_API_KEY=tps_…
export KURUMERA_AGENT=store        # optional: 'store' or 'builder'
```

**In a sandbox with an unwritable `$HOME`**, point the config elsewhere with
`KURUMERA_CONFIG_DIR=/tmp/kurumera`. The SDK only ever reads that file.

Mint a key with the scopes an agent should have:

```bash
python manage.py create_api_key --user you@example.com --name "sdk" \
  --scopes read_products write_products read_orders …
```

## Personas

`agent="store"` or `agent="builder"` narrows the tool set to exactly what the platform's
provisioned assistants get. It is a **cap, never a grant** — it can only take tools away
from a key, never add them.

```python
km = Kurumera(agent="builder")     # page-builder tools, read-only commerce
```

## Safety you can switch on

```python
km = Kurumera(read_only=True)             # refuses every writing tool, locally
km = Kurumera(allow_destructive=False)    # refuses deletes, publishes, overwrites
km = Kurumera(on_destructive=ask_a_human) # (tool, args, info) -> bool
```

`read_only=True` is the highest-value line in this file if your agent reads anything it did
not write. It makes an over-scoped key harmless for the duration of a reading task, and it
refuses before the request leaves the process. It **fails closed**: a tool this package has
never heard of is refused too.

These are advisory. The server enforces its own rules regardless — they exist so a
misdirected agent is stopped early and told which policy stopped it.

## Saying why

```python
with km.intent("Restocking after the spring sale"):
    km.bulk_update_products(product_ids=ids, action="set_status", value="ACTIVE")
```

The merchant sees that sentence beside the call in their activity feed. It is the difference
between *"something changed 40 products"* and an explanation.

## Results

A result behaves as the payload itself, because that is what every existing doc assumes:

```python
r = km.list_products(limit=5)
r["results"]           # the list
r.data                 # the same mapping
r.text                 # the first text block, verbatim
r.raw                  # the untouched envelope
```

Ten tools can also return an image — screenshots and product photos:

```python
shot = km.show_page_screenshot(page_ref="home", full_page=True)
shot.data["page_id"]
if shot.images:
    shot.images[0].save("home.png")
```

`shot.images` is empty when capture was skipped. That is a normal outcome, not an error —
a screenshot must never fail a write that already succeeded.

## Errors

```python
from kurumera import RateLimited, PermissionDenied, ConfirmationRequired

try:
    km.delete_product(product_id=pid)
except ConfirmationRequired:
    km.delete_product(product_id=pid, confirm=True)
except PermissionDenied as e:
    print("this key lacks", e.capability)
except RateLimited as e:
    print("slow down", e.retry_after, "seconds")
```

| Exception | When |
|---|---|
| `KurumeraConfigError` | no credential, bad URL, a tenant argument — nothing was sent |
| `AuthRequired` / `AuthFailed` | 401 |
| `SubscriptionInactive` | 402 — the store's subscription will not serve API traffic |
| `TenantInactive`, `NoTenant`, … | 403 |
| `PermissionDenied` | the key or role lacks the tool's capability; `.capability` names it |
| `ConfirmationRequired` | pass `confirm=True` |
| `InvalidArguments` | the arguments failed the tool's schema, server-side |
| `RateLimited` | 240 reads / 60 writes a minute; `.retry_after` when the server said |
| `ToolNotFound` | no such tool for this key |
| `ReadOnlyModeError`, `DestructiveBlockedError` | this client's own policy refused |

**Rate limits are not retried for you.** The window is a fixed minute and an agent sandbox
gives a script two; sleeping blind would spend most of your budget hiding a signal you
should act on. Pass `retry_on_rate_limit=True` if you are running a batch and mean it.

## Finding tools

```python
km.tools()                    # what THIS key may call — asks the server
km.describe("get_report")     # schema, hints, whether it needs confirm
km.search("invoice")          # offline, over names and descriptions
km.check()                    # is this package in step with the server?
```

`km.tools()` is the honest answer: the server has already filtered it by your key's
capabilities and persona. The offline list (`source="package"`) says nothing about what
*you* are allowed to do.

## Timeouts

The gateway in front of the platform closes a read at **60 seconds**, so the default client
timeout is 65 — just above it, so you get the server's honest error rather than a confusing
client-side abort. Raising it accomplishes nothing. Uploads are capped around 18 MB of
actual file once base64 inflation is counted.

## Versions

`kurumera.REGISTRY_TOOL_COUNT` and `kurumera.REGISTRY_HASH` say which tool registry this
build was generated from. A tool added to the platform since then is still callable:

```python
km.call("a_tool_added_last_week", some_argument=1)
```

An SDK version lag is a typing gap, not an outage.

## Releasing

The release procedure, the pre-flight gates and the three things about PyPI that cannot
be undone are in [PUBLISHING.md](PUBLISHING.md).
