@commercelayer/agent-cli 0.8.1 → 1.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -215
- package/dist/argv.js +214 -0
- package/dist/bin.js +15 -178
- package/dist/builtins/auth.js +169 -0
- package/dist/builtins/extensions.js +123 -0
- package/dist/builtins/index.js +8 -0
- package/dist/builtins/loops.js +215 -0
- package/dist/builtins/sandbox.js +186 -0
- package/dist/config.js +211 -0
- package/dist/confirm.js +56 -0
- package/dist/engine.js +173 -0
- package/dist/errors.js +146 -0
- package/dist/help.js +78 -0
- package/dist/http.js +117 -0
- package/dist/login.js +133 -0
- package/dist/resources.js +41 -0
- package/dist/schema.js +152 -0
- package/dist/schemas.js +72 -0
- package/dist/secrets.js +189 -0
- package/dist/version.js +4 -8
- package/package.json +4 -9
- package/dist/api/client.js +0 -190
- package/dist/api/server.js +0 -26
- package/dist/api/stub/db.js +0 -37
- package/dist/api/stub/http.js +0 -18
- package/dist/api/stub/index.js +0 -52
- package/dist/api/stub/locale.js +0 -52
- package/dist/api/stub/management.js +0 -409
- package/dist/api/stub/messages.js +0 -126
- package/dist/api/stub/orders.js +0 -625
- package/dist/api/stub/search.js +0 -78
- package/dist/api/stub/shopping.js +0 -673
- package/dist/api/stub/theme.js +0 -197
- package/dist/api/stub/ucp.js +0 -377
- package/dist/api/transport.js +0 -57
- package/dist/api/types.js +0 -4
- package/dist/assets.js +0 -100
- package/dist/catalog.js +0 -372
- package/dist/check.js +0 -187
- package/dist/commands.js +0 -1241
- package/dist/cursor.js +0 -31
- package/dist/files.js +0 -98
- package/dist/inbox/cursor.js +0 -11
- package/dist/inbox/fake.js +0 -95
- package/dist/inbox/imap.js +0 -139
- package/dist/inbox/mailbox.js +0 -25
- package/dist/inbox/message.js +0 -88
- package/dist/inspect.js +0 -237
- package/dist/languages.js +0 -49
- package/dist/links.js +0 -118
- package/dist/locales.js +0 -43
- package/dist/ops.js +0 -190
- package/dist/orderRequest.js +0 -141
- package/dist/output.js +0 -39
- package/dist/products.js +0 -151
- package/dist/promotions.js +0 -291
- package/dist/store.js +0 -379
- package/dist/storefront.js +0 -285
- package/dist/ucp/buy.js +0 -90
- package/dist/ucp/check.js +0 -362
- package/dist/ucp/client.js +0 -129
- package/dist/ucp/images.js +0 -62
- package/dist/ucp/platform.js +0 -90
- package/dist/ucp/shop.js +0 -357
- package/dist/ucp/signing.js +0 -67
- package/dist/web.js +0 -316
- package/dist/website.js +0 -390
package/README.md
CHANGED
|
@@ -1,246 +1,126 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
npx -y @commercelayer/agent-cli@latest init --name "The Blue Brand" # runs c11r init
|
|
7
|
-
```
|
|
8
|
-
## How it works
|
|
9
|
-
- **The repo is the store.** `.commercelayer/store.yml` holds the desired state (name, markets, theme, policies, and the products of a small store) and is committed; a large catalog goes in `.commercelayer/products/` as `.yml` or `.csv` files, images in `.commercelayer/img/`. `plan` shows what `apply` would change; `apply` makes the store match the files.
|
|
10
|
-
- **The website comes with the store.** Every store has a hosted theme at its preview URL, set by a few `theme:` settings and the product data: `apply` is all it takes. A custom storefront (your own web app) is opt-in: `storefront init`, `publish` and `check`.
|
|
11
|
-
- **JSON only.** Every command prints one JSON object. A failure exits 1 with `{ "error": { "code", "message", "hint" } }`; the hint is the fix.
|
|
12
|
-
- **Safe to re-run.** Every command is idempotent; a failed step can be fixed and run again.
|
|
13
|
-
- **Always current.** Run it as `npx -y @commercelayer/agent-cli@latest`, even where a `c11r` binary is installed; the note `init` writes in `AGENTS.md` and `CLAUDE.md` says so. The CLI sends `User-Agent: c11r/<version> (node/<major>)`, and an outdated one gets a `cli_outdated` entry in `warnings`, with `current`, `latest` and a `hint`.
|
|
14
|
-
- **No secrets in git.** `init` writes the sandbox token to `.commercelayer/credentials` (`C11R_ORG`, `C11R_TOKEN`) and gitignores it. The keys of a catalog source go in the same file.
|
|
15
|
-
## Commands
|
|
16
|
-
| Command | Does |
|
|
17
|
-
| --- | --- |
|
|
18
|
-
| `init [--name <business name>]` | Create a sandbox store, with no login. The name (the folder name when absent) names the organization; the org slug and store hostname derive from it, numbered when taken. Writes `store.yml` (a commented template) and `credentials`, plus a short `AGENTS.md` and `CLAUDE.md` section (appended to an existing file, never twice) that sends later agents in the repo to [commercelayer.ai/skills.md](https://commercelayer.ai/skills.md). Reuses the store if they exist. |
|
|
19
|
-
| `plan` | Validate `store.yml` and the product files, and list what `apply` would create, update or delete: markets, products, images, and the store's name, policies and theme. `missing_images` lists `img/<file>` references with no file in `.commercelayer/img/` (`apply` refuses them); `unused_images` lists files there that nothing references. |
|
|
20
|
-
| `apply [--allow-delete] [--full]` | Make the store match the files, the website included: the theme settings, and the images in `.commercelayer/img/` that the theme and products reference, uploaded when new or changed (unused ones are dropped from the store host). Refuses deletes without `--allow-delete`. Product changes go through a Commerce Layer import: `apply` waits for it, with progress as JSON lines on stderr, and prints one result. `apply_failed` lists the products Commerce Layer refused. While Commerce Layer is busy or unavailable, `apply` keeps reading (backing off on busy) and writes a `retrying` line. `apply_pending` means the import keeps going after 10 minutes, and `c11r status` follows it. An apply still running is finished before a new one starts. `--full` re-imports every product and price, after edits in the Commerce Layer dashboard. |
|
|
21
|
-
| `catalog pull` | Read the products from the catalog source in `store.yml` (a Supabase table, a Google Sheet, a Google Merchant Center feed, or a brand's website for a demo store) into `.commercelayer/products/catalog.csv`. Then `plan` and `apply` as usual. `--currency <code>` gives a website's currency when the site does not say it. |
|
|
22
|
-
| `storefront inspect <url>` | Read a brand's website, read-only: title, languages, theme colour, icons, `og:image`, logo candidates, the colours its CSS uses most, the currencies and countries it sells in, the platform (Shopify, WooCommerce or unknown) and whether `robots.txt` lets `catalog pull` read its products. For a demo store that looks like the site: the agent picks `theme.color` and `theme.logo` (a URL, hot-linked) from it. Writes no file. |
|
|
23
|
-
| `storefront init [--force]` | Custom storefronts only. Write a static storefront on [drop-in.js](https://commercelayer.github.io/drop-in.js/), no build step, wired to the sandbox's sales channel. `index.html`: the config block, the products grouped by top-level category with a navigation, a minicart that slides in on add to cart, the hosted cart and checkout, and a product page at `#/p/<sku>` built from `<template id="product-template">` and the `#products` data. `styles.css`: theme tokens first (`--paper`, `--ink`, `--muted`, `--line`, `--accent`, `--on-accent`, `--radius`, `--font`), which also restyle drop-in.js's prices, buttons and links, so a dark theme is a token change; then the drop-in.js overrides, then the page. `app.js`: the market switch, the category links and the product page. Product images are the store's, at `/img/<file>` (from `.commercelayer/img/`, uploaded by `apply`); products with no image show the tile the store host draws for them, at `/tiles/<sku>.svg`. The site's own files go in `storefront/assets/`. Refuses to overwrite without the flag; with it, rewrites the three files. |
|
|
24
|
-
| `storefront publish` | Custom storefronts only. Upload the folder at `storefront.path` (default `storefront`) to the store host, where it replaces the hosted theme, leaving out dotfiles. `/img/` on the store host is always the store's images, so files under the folder's `img/` are not served: publish lists them as `not_served`. |
|
|
25
|
-
| `storefront check` | Custom storefronts only. Read the published storefront at the preview URL and check it: drop-in.js and its config block wired to this store, the drop-in elements, an add to cart for every product and none for unknown skus, every image loading, alt text, stylesheets after drop-in.css, the theme tokens and drop-in overrides (no dead `::part` rules), title, `lang`, viewport, description, and whether the folder changed since publish. Prints `{ status, summary, checks: [{ id, kind, status: pass\|warn\|fail, message, fix, items }], render }` and exits 1 when a check fails; warnings do not fail. `kind` is `static` today; a later rendering phase adds `render` checks and fills `render`. |
|
|
26
|
-
| `promotions list` · `show <id>` | The store's promotions, live in Commerce Layer: id, name, status, rules, actions, uses, dates; `managed: false` for ones made in the dashboard. |
|
|
27
|
-
| `promotions create` · `update <id>` | Create a promotion, or replace one, from a spec in YAML or JSON on stdin or `-f <file>` (see [Promotions](#promotions)). Prints the promotion and the `orders test` call that checks it. |
|
|
28
|
-
| `promotions enable <id>` · `disable <id>` · `delete <id>` | Turn a promotion on or off, or delete it (ask the human first). |
|
|
29
|
-
| `links list` · `show <id>` | The store's links, live in Commerce Layer: url, id, name, status, what it sells, markets, dates; `managed: false` for ones made in the dashboard. |
|
|
30
|
-
| `links create` | Create a shareable link to a microstore (sku, skus or category) or an order's checkout, from a spec in YAML or JSON on stdin or `-f <file>` (see [Links](#links)). Prints the link, `url` first. |
|
|
31
|
-
| `links enable <id>` · `disable <id>` · `delete <id>` | Turn a link on or off, or delete it (ask the human first). |
|
|
32
|
-
| `orders test [--sku <id>] [--quantity <n>] [--country <CC>] [--code <discount>] [--via-cart] [--decline]` | Buy from the store over UCP as a shopping agent would: discover, search, (cart,) checkout, address, pay with the sandbox handler, read the order back. Prints the order, the `discounts` applied (promotions and codes, in minor units like the totals) and every step. |
|
|
33
|
-
| `orders list [--status pending\|placed\|approved\|cancelled\|editing\|all] [--channel message\|link] [--payment <status>] [--fulfillment <status>] [--archived [all]] [--limit <n>]` | The store's orders, live in Commerce Layer, newest first; by default placed and approved, not archived, 25 at most (see [Orders and fulfillment](#orders-and-fulfillment)). Pending orders (from emails and messages) only with `--status pending`. |
|
|
34
|
-
| `orders create -f <file> \| <flags> --source email:<id> [--place\|--hold] [--force]` | An order from an email or a message: the agent writes the request (customer, address, lines by sku or by the words used); the API resolves the products at catalog prices and makes it pending, or answers `needs_review` with nothing created (see [Orders from emails and messages](#orders-from-emails-and-messages)). |
|
|
35
|
-
| `orders place <n>` | Place a pending order once the human said yes. |
|
|
36
|
-
| `orders show <number or id>` | One order as the merchant sees it: line items, totals, addresses, payment and transactions, shipments, and `next`, what it waits for. |
|
|
37
|
-
| `orders approve <n> [--capture]` · `capture` · `cancel` · `refund <n> [--amount <major>]` · `archive` · `unarchive` | Move an order; each prints the order after it. |
|
|
38
|
-
| `fulfillment list [--status <status>\|all] [--limit <n>]` · `show <shipment>` | The shipments, by default the ones not shipped yet. A shipment is its number (`1001/S/001`), its id, or its order's number. |
|
|
39
|
-
| `fulfillment hold` · `pick` · `pack` · `ready` · `ship <shipment> [--carrier] [--tracking] [--tracking-url]` · `deliver` | Move a shipment; each prints the shipment after it. |
|
|
40
|
-
| `ops check [--since 24h\|7d\|<ISO>]` | What is new since the last check and what the policy in `.commercelayer/ops.yml` would do, without changing anything: new orders, what it would approve and capture, what it holds for the human and why, orders with no payment yet, stuck shipments (see [Running the store on a schedule](#running-the-store-on-a-schedule)). |
|
|
41
|
-
| `ops run [--dry-run]` | Apply the policy: approve, and capture if it says so, every placed order it does not hold. Never ships, cancels or refunds. |
|
|
42
|
-
| `ops digest [--since 24h\|7d\|<ISO>]` | A summary for the human: orders, revenue per currency, what was approved, what is held or stuck, top products, as `summary` text. |
|
|
43
|
-
| `insights [--since 24h\|7d\|30d\|<ISO>] [--until <ISO>] [--by product\|day\|market\|country\|channel\|promotion]` | Sales over a span (default 30 days): orders, units, revenue and average order per currency, refunds and cancellations apart, top products, channels, and with `--by` one row per product, day, market, country, channel or promotion; a `summary` to read. |
|
|
44
|
-
| `inbox check` · `list [--unseen] [--since 24h] [--limit 20]` · `pull <id>\|--unseen` · `tag <id> [--label <name>]… [--unlabel <name>]… [--move <folder>] [--seen]` | The inbox orders arrive in, over IMAP: list it, save messages and their attachments under `.commercelayer/inbox/` for the agent to read, label or move them when done. No sending: replies stay with the human. |
|
|
45
|
-
| `ucp profile [--agent]` | The store's UCP business profile (`/.well-known/ucp`); with `--agent`, this CLI's own platform profile. |
|
|
46
|
-
| `ucp search [<words>] [--category <name>] [--min-price <n>] [--max-price <n>] [--country <CC>] [--images]` · `ucp product <id> [--images]` | Buy for the human, step by step (see [Buying for the human](#buying-for-the-human)): the store's products with their categories, prices in major units in `price.display`, sale prices, shipping and the market the prices are for. |
|
|
47
|
-
| `ucp checkout create --item <variant id>[:<qty>] … [--code <discount>] [--country <CC>]` | Open a checkout; `messages` say what is still missing (email, shipping address). |
|
|
48
|
-
| `ucp checkout update <id> [--email …] [--street …] [--item <variant id>:<qty>] [--code …] [--shipping <option>]` | Give the buyer's details (flags, or YAML or JSON on stdin or `-f`), change items or codes; `ready_for_complete` once nothing is missing, with shipping in the total. |
|
|
49
|
-
| `ucp checkout show <id>` · `cancel <id>` · `complete <id> [--decline]` | Read, cancel, or pay a checkout with the sandbox handler (after the human said yes): `order` has `label` and `permalink_url`. |
|
|
50
|
-
| `ucp order <id>` · `ucp ask "<question>"` | Read an order the CLI placed; ask the store a question. Every `ucp` buying command takes `--store <host>` to buy from any store that speaks UCP. |
|
|
51
|
-
| `ucp check` | Walk every UCP capability the store advertises and report what passes. Places and pays one real test order on the way (the output says which). Exits 1 on a failed check. |
|
|
52
|
-
| `status` | The sandbox, the sales channel client id, the published storefront, what is applied, the last import job (`apply`), the pending changes (exactly what `plan` lists; a store that does not load is pending too, with the error), orders, the store's semantic search index (`search`: model, status, vectors, embedded, updated_at, error; null before its first run), the UCP profile URL. |
|
|
53
|
-
| `claim` | Start the device flow that keeps the sandbox: a URL and a code for the human. |
|
|
54
|
-
| `help [<command>]` | Describe the commands. `--version` prints the version and `latest_hint`, how to always run the newest. |
|
|
55
|
-
## store.yml
|
|
56
|
-
`init` writes a commented template. A filled-in store looks like this:
|
|
57
|
-
```yaml
|
|
58
|
-
name: Beans
|
|
59
|
-
markets:
|
|
60
|
-
# One market per region you sell in. countries are ISO 3166 codes.
|
|
61
|
-
# shipping is optional: without it orders ship free as "Standard".
|
|
62
|
-
- code: eu
|
|
63
|
-
name: Europe
|
|
64
|
-
countries: [IT, FR, DE, ES]
|
|
65
|
-
currency: EUR
|
|
66
|
-
shipping: { method: Standard, price: 4.90, days: 3-5 }
|
|
67
|
-
- code: us
|
|
68
|
-
name: United States
|
|
69
|
-
countries: [US]
|
|
70
|
-
currency: USD
|
|
71
|
-
products:
|
|
72
|
-
# sku is unique and stays the same when you rename a product.
|
|
73
|
-
- sku: COFFEE-ETH-250
|
|
74
|
-
name: Ethiopia Yirgacheffe, 250 g
|
|
75
|
-
description: Washed, floral, bright.
|
|
76
|
-
image_url: img/eth.jpg # .commercelayer/img/eth.jpg, or an https:// URL
|
|
77
|
-
prices: { EUR: 14.50, USD: 16.00 }
|
|
78
|
-
theme:
|
|
79
|
-
# The hosted website. Every setting is optional.
|
|
80
|
-
color: "#2f5d50" # the accent: buttons, links, the hero band; text on it is black or white by contrast
|
|
81
|
-
logo: img/logo.svg # or an https:// URL; default: the name as a wordmark
|
|
82
|
-
hero: img/hero.jpg # the home page's wide image; default: a band in the accent
|
|
83
|
-
tagline: Small-batch coffee, roasted in Milan # at most 120 characters; default: "Ships to <countries>"
|
|
84
|
-
badge: Demo # a short label next to the logo, at most 20 characters
|
|
85
|
-
storefront:
|
|
86
|
-
mode: hosted # default; agent-only: only UCP and the pages it needs, no catalog pages
|
|
87
|
-
policies:
|
|
88
|
-
# Pages shopping agents show buyers before they pay. Without them the store
|
|
89
|
-
# serves default sandbox pages at <preview_url>/policies/<name>.
|
|
90
|
-
privacy_policy: https://example.com/privacy
|
|
91
|
-
terms_of_service: https://example.com/terms
|
|
92
|
-
returns: https://example.com/returns
|
|
1
|
+
# c11r
|
|
2
|
+
|
|
3
|
+
The Commerce Layer CLI for coding agents. One grammar from a sandbox to a running organization:
|
|
4
|
+
|
|
93
5
|
```
|
|
94
|
-
|
|
95
|
-
Promotions are live settings in Commerce Layer, not store files: the `promotions` commands create and change them directly, and nothing about them is committed. A spec, in YAML or JSON on stdin or with `-f <file>`:
|
|
96
|
-
|
|
97
|
-
```yaml
|
|
98
|
-
id: welcome10 # the handle: lowercase letters, digits, dashes; unique
|
|
99
|
-
name: Welcome, 10% off
|
|
100
|
-
rules: [{ coupon: WELCOME10 }]
|
|
101
|
-
actions: [{ percentage: 10 }]
|
|
6
|
+
c11r <noun> <verb> [id] [--field value ...]
|
|
102
7
|
```
|
|
103
8
|
|
|
104
|
-
|
|
9
|
+
This is the 1.0.0 line, in development on the `clean-slate` branch. It replaces every earlier command; nothing of 0.x is kept.
|
|
105
10
|
|
|
106
|
-
|
|
107
|
-
- Actions: `percentage: 10` (with `skus` or `category` to limit it), `fixed_amount: 5`, `free_shipping`, `fixed_price: { price, skus|category }`, `free_gift: { skus|category, quantity }`, `buy_x_pay_y: { x, y, cheapest_free?, skus|category }`. Actions without their own skus take the rule's. Amounts are in major units, like prices; in a store with several currencies, a promotion with an amount names its `market`. Commerce Layer gets one promotion per action.
|
|
108
|
-
- `flex: { rules: [...] }` holds a Commerce Layer Rules Engine promotion as written (amounts in cents), in place of `rules` and `actions`. Sandboxes have no Rules Engine: `create` answers `flex_unavailable` and creates nothing.
|
|
11
|
+
## The grammar
|
|
109
12
|
|
|
110
|
-
|
|
13
|
+
- Standard verbs: `list`, `get`, `create`, `update`, `delete`. Nouns with a state machine add their own verbs: `c11r markets disable eu`, `c11r orders approve 1001`, `c11r payments capture 1001`.
|
|
14
|
+
- A plain noun has no code in the CLI. Its fields, filters and verbs come from the API's schema, and `c11r schema <noun>` returns that schema.
|
|
15
|
+
- Flags are the schema's fields, in kebab or snake case: `--price-list` and `--price_list` are the same.
|
|
16
|
+
- On `list`, `--<field> <value>` filters by equality and `--<field>.<op> <value>` by another operator: `--placed-at.gte 2026-11-01`, `--status.in placed,approved`. `--waiting` lists what needs a decision now.
|
|
17
|
+
- A dotted flag sets part of an object: `--customer.email ada@example.com`. An array takes a JSON array, values separated by commas, or the flag repeated; an array of objects takes `key=value` pairs, one object per flag: `--items sku=COFFEE,quantity=2`. `--data` takes the fields as one JSON object, or `@` and a file name.
|
|
18
|
+
- Amounts are in major units: `--amount 12.50`.
|
|
111
19
|
|
|
112
|
-
|
|
113
|
-
Links are live settings in Commerce Layer too, never store files: shareable URLs that open a hosted microstore for one sku, a set of skus or a category, or the hosted checkout for an order. A spec, in YAML or JSON on stdin or with `-f <file>`:
|
|
20
|
+
## Built-in nouns
|
|
114
21
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
22
|
+
| Command | What it does |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `c11r status` | Where this machine and folder stand: the organization picked and from where, its state, plan, mode, limits, usage and what is waiting; the stored login, if any. |
|
|
25
|
+
| `c11r schema <noun>` | The noun's schema document: fields, filters, verbs, which verbs are destructive and which a loop may automate. |
|
|
26
|
+
| `c11r sandbox create [--name] [--seed]` | An anonymous sandbox. Its token is stored on this machine and never printed; the folder is linked to it. |
|
|
27
|
+
| `c11r sandbox get`, `reset`, `claim`, `destroy` | The sandbox's details; a fresh organization behind the same token; a claim URL for the human; the end of the sandbox. |
|
|
28
|
+
| `c11r loops create <name> --skill --every --allow [--max-total] [--market]` | A loop in `loops.yml`: a skill, a policy and a cadence. Also `list`, `get`, `update`, `delete`. |
|
|
29
|
+
| `c11r loops run <name>` | One pass of a loop. The CLI is not a daemon: a scheduler repeats the pass. |
|
|
30
|
+
| `c11r report sales [--from] [--to] [--by] [--interval] [--market] [--top]` | Sales in a window, from the Metrics API. A plain noun: its flags come from the API's schema. |
|
|
31
|
+
| `c11r auth login [--client-id] [--secret-file] [--keep-file]` | The human's own Commerce Layer login, from their Provisioning API application. See below. |
|
|
32
|
+
| `c11r auth logout`, `auth switch <slug> [--global]` | Forgetting the login; the organization commands act on. |
|
|
120
33
|
|
|
121
|
-
|
|
34
|
+
A loop policy allows verbs: a bare verb is the skill's own noun (`approve`), `<noun>.<verb>` another Manage noun's verb on the same item (`payments.capture`). Verbs that delete or return money are never open to a policy.
|
|
122
35
|
|
|
123
|
-
|
|
124
|
-
Beyond a handful of products, put the catalog in `.commercelayer/products/` and leave `products` out of `store.yml`. Every file in the folder is merged into the catalog by sku, in file-name order:
|
|
125
|
-
- `<name>.yml`: a list of products in the shape above (one file per collection, or one per product, as you like).
|
|
126
|
-
- `<name>.csv`: one product per row. The first line names the columns: `sku`, `name`, `description`, `image_url`, `category`, and one column per currency, named by its ISO 4217 code, holding the price. Quoted cells and CRLF are fine; empty cells are left out.
|
|
127
|
-
```csv
|
|
128
|
-
sku,name,description,category,EUR,USD
|
|
129
|
-
COFFEE-ETH-250,"Ethiopia Yirgacheffe, 250 g","Washed, floral, bright.",Coffee > Single origin,14.50,16.00
|
|
130
|
-
COFFEE-COL-250,"Colombia Huila, 250 g",,Coffee > Single origin,12.00,13.50
|
|
131
|
-
```
|
|
132
|
-
Errors name the file (and line, for CSV) and the sku, so one product file can be fixed without reading the rest. Moving a product between `store.yml` and a file is not a change to the store: `plan` keys everything by sku. The catalog has no stock: inventory is tracked live in Commerce Layer, and the sandbox treats every product as in stock. Prices in these files are what the store charges; a published storefront reads prices and availability live from Commerce Layer.
|
|
133
|
-
Rules `plan` and `apply` enforce: a name, at least one market and one product; every market has `code`, `name`, `countries` and an ISO 4217 `currency`; codes and skus are unique across all files; a product has only `sku`, `name`, `description`, `image_url`, `category` and `prices`; a `category` is a path of names from the top level, separated by ` > `; an `image_url` is an `https://` URL or a path in the storefront folder such as `img/eth.jpg` (served from the store host, given to shopping agents as a full URL); every product has a price in the currency of every market; `shipping`, if present, has `method` and a non-negative `price`; policy entries are URLs. Currency, languages and tax follow from the countries.
|
|
134
|
-
### Languages
|
|
135
|
-
`language:` at the top of `store.yml` is the language the products are written in (ISO 639-1, default `en`). A product's `name`, `description` and `category` can also be a map with the base language and others, and a CSV takes `name_<ll>`, `description_<ll>` and `category_<ll>` columns next to the plain ones:
|
|
136
|
-
```yaml
|
|
137
|
-
language: en
|
|
138
|
-
products:
|
|
139
|
-
- sku: ETH
|
|
140
|
-
name: { en: Ethiopia, it: Etiopia }
|
|
141
|
-
category: { en: Coffee > Single origin, it: Caffè > Monorigine } # the same levels, each translated
|
|
142
|
-
prices: { EUR: 14.50 }
|
|
143
|
-
```
|
|
144
|
-
A store has at most 6 languages, the base one included. Every country gets its own pages, `/<cc>-<ll>/` (`/it-it/`), in that country's language: the product texts in it when the product has them, the base language otherwise; `/` lets the buyer choose the country when there are several. Shopping agents get the same texts over UCP, in the language they ask for. `plan` warns about a language no country of the store speaks. A store without translations plans and applies as before.
|
|
145
|
-
|
|
146
|
-
### A catalog the human edits
|
|
147
|
-
When the products live in a tool the human edits too, `store.yml` names it and `c11r catalog pull` copies it into `.commercelayer/products/catalog.csv`, sorted by sku: same source, same file. Pull applies the product rules above before it writes, with errors naming the row in the source, and reports the skus added, changed and removed, and `categories`: the category paths, in the file's order, each once (named in `next` when there are at most 12). Removed products leave the store only through `apply --allow-delete`. Every source has the same columns: `sku`, `name`, `description`, `image_url`, `category`, and one price column per market currency, named by its ISO 4217 code in lowercase (`eur`, `usd`).
|
|
148
|
-
```yaml
|
|
149
|
-
catalog:
|
|
150
|
-
source: sheets # sheets | supabase | merchant | website
|
|
151
|
-
url: https://docs.google.com/spreadsheets/d/e/2PACX-.../pub?output=csv
|
|
152
|
-
# table: products # supabase only
|
|
153
|
-
# limit: 100 # website only: how many products (at most 500)
|
|
154
|
-
```
|
|
155
|
-
- `sheets`: a Google Sheet, by its CSV link (File > Share > Publish to web > Comma-separated values) or by its own link when anyone with the link can view it. No key. Translations are `name_<ll>`, `description_<ll>` and `category_<ll>` columns, in sheets and Supabase tables alike.
|
|
156
|
-
- `supabase`: a table (default `products`), read over the REST API with the project's anon key: `C11R_SUPABASE_URL=https://<project>.supabase.co` and `C11R_SUPABASE_KEY=<anon key>` in `.commercelayer/credentials`, or the environment. The key needs a row level security policy that allows select. Never use the `service_role` key. Columns `id`, `created_at` and `updated_at` are skipped.
|
|
157
|
-
- `merchant`: a Google Merchant Center product feed URL, RSS 2.0 (XML, `g:` namespace) or TSV. `id` is the sku, `title` the name, `description`, `image_link` the image, `price` (`14.50 EUR`) the price in its currency, `product_type` the category. An id listed once per currency is one product with several prices. Other attributes, `sale_price` included, are ignored. Other languages: `catalog.translations: { it: <the Italian feed URL> }`, one feed per language, merged by `id` (`title`, `description`, `product_type`).
|
|
158
|
-
- `website`: a brand's own website, for a demo store (the human's site, or a brand they are pitching to). `url` is the site, `limit` how many products (default 100, at most 500). Pull tries, in order, and the first that yields products wins: Shopify's public `products.json` (also `/collections/all/products.json`), WooCommerce's Store API (`/wp-json/wc/store/v1/products`), then the product pages its sitemap lists (`/products/`, `/product/`, `/p/`, `/shop/`, `/item/`) and their schema.org Product JSON-LD. One row per product: the first available variant's sku and price (or the handle in upper snake case), the title, the description as plain text (paragraphs kept, at most 2,000 characters), the first image, the product type or breadcrumb as category. Products without a price are left out, and a sku seen twice keeps the first. Prices are the site's, in the site's currency, never converted: the store's markets must sell in it (`currency_mismatch` otherwise), and when the site does not say it, `currency_unknown` asks for `--currency <code>`. Translations are not pulled yet; `source_site.languages` lists the site's languages. **Images and the logo stay on the brand's servers: `image_url` links them, nothing is copied.** Pull reads politely: `robots.txt` is honoured for every path (`robots_skipped` counts what it disallows), one request at a time and at most 2 a second, 15 s per request and 60 s in all (`budget_exhausted` when it stopped early, with what it read), as `c11r/<version> (+https://commercelayer.ai)`. It prints `strategy`, `source_site: { url, title, platform, product_count_seen, currency, languages }`, `pulled`, `categories`, `variants_skipped`, `robots_skipped`, `no_price_skipped`, `duplicates_skipped`, `other_currency_skipped`, `tried: [{ strategy, result }]` and `requests`, so the agent can tell the human "I found 340 products, pulled 100".
|
|
159
|
-
Error codes: `no_catalog_source`, `invalid_catalog`, `missing_credentials`, `catalog_unreachable`, `catalog_unauthorized`, `catalog_not_found`, `catalog_not_csv`, `catalog_error`, `empty_catalog`, plus `invalid_product` and `missing_price`; for a website `site_unreachable`, `no_products_found` (ask the human for a feed or a sheet instead), `currency_unknown` and `currency_mismatch`.
|
|
36
|
+
## Login, organization tokens and secrets
|
|
160
37
|
|
|
161
|
-
|
|
38
|
+
Two kinds of bearer. An **organization token** (`sbx_...` from `sandbox create`, `org_...` from `organizations create` and `organizations connect`) acts on one organization's data. The **login** is the human's own Provisioning API application, for `organizations`, `members`, `roles` and the `credentials` of a claimed organization. Each verb's schema says which it takes (`verbs[].credential`), and the CLI sends that one.
|
|
162
39
|
|
|
163
|
-
|
|
164
|
-
The theme renders the home page (logo, hero with name and tagline, products by top-level category), a page per category at `/categories/<slug>/...` and per product at `/products/<sku>`, with the market from `?market=<code>`, cart and checkout. Images written `img/<file>` (`theme.logo`, `theme.hero`, a product's `image_url`) are files in `.commercelayer/img/`, served at `<preview_url>/img/<file>`: paths of letters, digits, `.`, `_`, `-` and `/`, a lowercase `png`, `jpg`, `jpeg`, `webp`, `gif`, `avif` or `svg` extension, at most 5 MB each. `apply` uploads the new and changed ones (by SHA-256) before anything else, and the store host drops the ones nothing references any more.
|
|
40
|
+
`c11r auth login` reads the client id and the client secret:
|
|
165
41
|
|
|
166
|
-
|
|
42
|
+
- in a terminal, typed at a prompt; the secret is not echoed;
|
|
43
|
+
- with no terminal, from a file: `--secret-file <path>`, or by default `~/.config/c11r/provisioning.secret`. The file holds `client_id=...` and `client_secret=...` lines (or JSON with those keys, or the secret alone, with `--client-id`). c11r reads it only when no other user can (mode 600), and deletes it once read unless `--keep-file` is given;
|
|
44
|
+
- with no terminal and no file, it stops at once with `secret_required`, `next_step.action` `human_step` and the dashboard page in `next_step.url`, and makes the file, mode 600, as two lines for the human to complete: `client_id=` and `client_secret=`. A file with either value missing gets the same answer, and nothing is sent to Commerce Layer. Then `c11r auth login` reads it.
|
|
167
45
|
|
|
168
|
-
|
|
46
|
+
Never from a flag or the environment. c11r exchanges the pair for a two hour token at Commerce Layer and sends only that token to the c11r API; the client secret never leaves this machine. Both are kept in `credentials.json`, mode 600, and never printed. `c11r status` shows the stored login as `cli.login`, `{ client_id, email, stored_at }`, or null: read on this machine, never the secret. `auth logout` forgets the login; its last token ends by itself within two hours. Organization tokens stay.
|
|
169
47
|
|
|
170
|
-
|
|
48
|
+
`organizations create` and `organizations connect <slug>` answer an organization token once: it is stored like a sandbox token. `connect --revoke-others` also ends every other token of that organization, and asks for confirmation. With a login and no organization token for the organization named, an organization noun stops with `no_credentials` and the next step `c11r organizations connect <slug>`. `members` and `roles` with an organization token and no login are sent with that token, and the API says what is needed: a claim for a sandbox, a login for a claimed organization. `organizations` with no login stops here with `login_required`.
|
|
171
49
|
|
|
172
|
-
|
|
173
|
-
## Orders and fulfillment
|
|
50
|
+
`credentials create` and `credentials token` answer a secret once. c11r writes it to a file with mode 600 and prints the file, the format and a hint, never the value: by default `~/.config/c11r/secrets/<org>/<name>.env` (`<id>-token.env` for a token), or `--out <path>`, as `KEY=value` lines or with `--format json` one JSON object. The file is checked before the request is sent; one there already is replaced only with `--yes`. A file inside a git repository that git does not ignore gets the warning `secret_not_ignored`. With `--dry-run` nothing is written and `secret_file` says where it would go. `credentials delete` removes the files c11r wrote for that credential in `~/.config/c11r/secrets/<org>/` (its secret and its tokens) and lists them in `removed_files`; `sandbox destroy` removes the whole folder of that organization and names it in `forgotten.secrets`. A file written with `--out` is not tracked and stays: the warning `out_files_not_tracked` says so, when a secret of that organization was written with `--out` on this machine, or when a delete found nothing to remove. A token file carries `CL_BASE_ENDPOINT` beside the token. `sales-calls book` answers a cancel token once: it is kept in `credentials.json` and `sales-calls cancel <booking>` sends it.
|
|
174
51
|
|
|
175
|
-
|
|
52
|
+
## Global flags
|
|
176
53
|
|
|
177
|
-
|
|
54
|
+
| Flag | What it does |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `--json` | JSON output. The default and the only format. |
|
|
57
|
+
| `--dry-run` | On a write, the CLI sends it with `?dry_run=true`: the API checks it all (state, plan, limits, Commerce Layer's current status) and answers what it would do, `would: [...]`, and nothing is written. No confirmation is needed. Reads ignore it. |
|
|
58
|
+
| `--yes` | Confirms a destructive verb in advance. |
|
|
59
|
+
| `--org <slug>` | The organization to act on, by its stored token. |
|
|
60
|
+
| `--live` | Live data, on a claimed organization: `?mode=live` on every request. Without it, test mode. |
|
|
61
|
+
| `--help` | Describes the noun or verb it follows, from its schema. |
|
|
178
62
|
|
|
179
|
-
|
|
63
|
+
Destructive verbs (deletes, `orders cancel`, `payments void` and `refund`, `sandbox reset` and `destroy`) ask for confirmation in a terminal, then send `confirm=true`. With no terminal, as when an agent or a schedule runs the CLI, they never wait: without `--yes` they stop at once with `confirmation_required` and exit code 3.
|
|
180
64
|
|
|
181
|
-
|
|
65
|
+
## Which organization
|
|
182
66
|
|
|
183
|
-
|
|
184
|
-
auto_approve: true # approve placed orders (default false: every order waits for the human)
|
|
185
|
-
auto_capture: true # take the payment when approving, and of orders the human approved (default false)
|
|
186
|
-
review_above: 500 # hold orders above this total, in major units of the order's currency
|
|
187
|
-
hold_countries: [US] # hold orders shipping to these countries
|
|
188
|
-
stuck_days: 2 # a shipment picking, packing or ready to ship longer is stuck (on hold never is)
|
|
189
|
-
transit_days: 7 # a shipment shipped longer and not delivered is stuck in transit
|
|
190
|
-
mail_orders: # orders made from emails and messages (orders create)
|
|
191
|
-
auto_place: false # place them at once (default false: pending until the human says yes)
|
|
192
|
-
labels: { pending: pending, ordered: ordered, declined: declined } # what the agent labels the emails (these by default)
|
|
193
|
-
```
|
|
67
|
+
`--org`, then `C11R_ORG`, then `.c11r.json` in the project folder (found by walking up, like `.git`), then the default set by `auth switch --global`, then the stored organization when exactly one is stored (never for a login noun's `?org=`). With several stored and nothing naming one, the command stops with `org_required` and lists them.
|
|
194
68
|
|
|
195
|
-
|
|
69
|
+
- Tokens are only in `~/.config/c11r/credentials.json` (or under `XDG_CONFIG_HOME`), mode 600. The CLI never prints one, only its hint.
|
|
70
|
+
- `.c11r.json` holds only `{ "org": "<slug>" }`: no secret, safe to commit. `sandbox create`, `organizations create` and `auth switch` write it, and `organizations connect` when the folder has no link; `reset` follows the new slug; `destroy` removes it when it names the destroyed organization.
|
|
71
|
+
- The project folder is the one with `.c11r.json`, else the nearest with `store.yml` or `loops.yml`, else the current one. `loops.yml` goes there.
|
|
196
72
|
|
|
197
|
-
|
|
73
|
+
## Output, errors and exit codes
|
|
198
74
|
|
|
199
|
-
|
|
75
|
+
Every answer is one JSON object on stdout, the API's envelope:
|
|
200
76
|
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
customer: { email: buyer@bar.example, company: Bar Centrale, phone: "+33 1 23 45 67 89" }
|
|
204
|
-
ship_to: { line_1: 3 rue de la Paix, city: Paris, postal_code: "75002", country: FR }
|
|
205
|
-
items:
|
|
206
|
-
- { sku: ETH, quantity: 6 }
|
|
207
|
-
- { query: white mug, quantity: 2 } # the words the customer used
|
|
208
|
-
reference: PO-2026-114 # their purchase order number
|
|
77
|
+
```json
|
|
78
|
+
{ "data": {}, "state": { "lifecycle": "sandbox", "plan": null, "mode": "test", "org": "zztest-blue-beans", "expires_at": "2026-11-17T00:00:00Z" }, "warnings": [], "next": [] }
|
|
209
79
|
```
|
|
210
80
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Transport rules the store enforces: `UCP-Agent` (an HTTPS platform profile URL) and `Request-Id` on every request; `Idempotency-Key` on every state change, replayed when the body matches and `409` when it does not; `Content-Digest` verified when present. Every service response carries `Content-Digest`, `Signature-Input` and `Signature` (ES256 over `@status`, `content-digest`, `content-type`, key `kid` from the profile). Orders are private: readable by the platform that placed them or with a buyer token carrying an order scope; anything else gets `401` with `WWW-Authenticate: Bearer`. Catalog ids are the skus (`product.id` is `prod_<sku>`, `variant.id` is the sku); amounts are integers in minor units.
|
|
228
|
-
The CLI is a UCP **platform**: its profile is served at `https://commercelayer.ai/ucp/agent-cli/profile.json` (`ucp profile --agent` prints it) and goes in `UCP-Agent` on every call. The store negotiates the capability intersection with it. `orders test` is the buyer; `ucp check` runs 30-odd checks across profile, transport rules, catalog, cart, discount, checkout, fulfillment, consent, payment, signatures, idempotency, order, identity linking, ask and permalink.
|
|
229
|
-
### Buying for the human
|
|
230
|
-
|
|
231
|
-
An agent can buy from any UCP store on behalf of its human, one step per command, talking to the human in between: `ucp search` to show the products (words in any order; `--category`, which includes the subcategories, and `--min-price`/`--max-price` in major units narrow it; the `messages` the store sends, such as its categories when nothing matched, are passed on), `ucp checkout create --item <variant id>:<qty>` for what they picked (the `variants[].id` search lists; the sku on Commerce Layer stores), `ucp checkout update <id>` with the email and shipping address they give (never invented), then, once they said yes to the total, `ucp checkout complete <id>`, and the order's `label` and `permalink_url` back to them. Money comes as `{ amount, currency, major, display }`: `amount` in minor units as UCP has it, `display` for the human. The store is `--store <hostname or origin>`, or this folder's store when there is one; checkout and order ids are remembered with their store (in `~/.local/state/c11r/ucp.json`), so later steps need no `--store`. Paying needs the store's sandbox handler (`ai.commercelayer.sandbox`, test payments only): any store without it (every store taking real payments) answers `payment_handler_unsupported`, and the human pays at the checkout's `continue_url`. Responses are signature-checked like `orders test`: `signature` is `verified`, `invalid`, `unknown_key` or `unsigned`.
|
|
232
|
-
|
|
233
|
-
`--images` is for agents that can read image files and want to look at or describe a product: it saves each product's `image_url` under `<tmpdir>/c11r/images/<store host>/` (the first 24, in parallel, 5 s each, a file already there reused) and adds `image_file`, the absolute path, or `image_error`. Showing a thumbnail in a chat needs only `image_url`, which checkout and order line items carry too.
|
|
81
|
+
The CLI's own answers (help, loops.yml) have the same shape, with `state` null. Every error, the API's or the CLI's, is:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"error": {
|
|
86
|
+
"code": "state_required",
|
|
87
|
+
"message": "Live mode needs a claimed organization; this is a sandbox.",
|
|
88
|
+
"required_state": "claimed",
|
|
89
|
+
"required_plan": null,
|
|
90
|
+
"missing": null,
|
|
91
|
+
"path": null,
|
|
92
|
+
"next_step": { "action": "claim", "command": "c11r sandbox claim", "url": null }
|
|
93
|
+
},
|
|
94
|
+
"state": { "lifecycle": "sandbox", "plan": null, "mode": "test", "org": "zztest-blue-beans", "expires_at": "2026-11-17T00:00:00Z" }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
234
97
|
|
|
235
|
-
|
|
98
|
+
`code` is stable. `missing` is `{ noun, why }`, `path` the flag or field at fault, `next_step.action` one of `create_sandbox`, `claim`, `upgrade`, `contact_sales`, `create_dependency`, `fix_request`, `retry_later`, `human_step`, `none`. `human_step` is something only the human can do, such as saving a secret to a file, with a page in `url` when there is one. The CLI may add `did_you_mean` and `details`.
|
|
236
99
|
|
|
237
|
-
|
|
100
|
+
| Exit | Meaning | Codes |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| 0 | done | |
|
|
103
|
+
| 1 | unexpected failure: a bug in c11r, or a secret the disk refused after the API answered it | `secret_not_written` |
|
|
104
|
+
| 2 | the command is not valid | `unknown_noun`, `unknown_verb`, `unknown_field`, `invalid_value`, `missing_value`, `missing_argument`, `missing_id`, `unexpected_argument`, `no_fields`, `org_required`, `invalid_config`, `secret_required`, `insecure_file` |
|
|
105
|
+
| 3 | a destructive verb needs confirmation | `confirmation_required`, `confirmation_declined` |
|
|
106
|
+
| 4 | the organization needs another state or plan, has expired, or reached a limit | `state_required`, `plan_required`, `expired`, `limit_reached` |
|
|
107
|
+
| 5 | no such resource | `not_found`, `claim_not_found` |
|
|
108
|
+
| 6 | the API or Commerce Layer refused the request as it is | `invalid_argument`, `invalid_transition`, `conflict`, `dependency_missing`, `commercelayer_refused`, `policy_forbidden`, `not_supported`, `forbidden` |
|
|
109
|
+
| 7 | the API did not answer, is busy, or failed | `api_unreachable`, `rate_limited`, `commercelayer_busy`, `commercelayer_unavailable`, `api_error`, `schema_unavailable`, `invalid_response` |
|
|
110
|
+
| 8 | a wrong or revoked token, no token stored for the organization named, or no login | `unauthorized`, `no_credentials`, `login_required` |
|
|
238
111
|
|
|
239
|
-
|
|
112
|
+
`rate_limited` is retried twice with a short backoff, for reads and writes: nothing was processed. `commercelayer_busy` and `commercelayer_unavailable` are retried for reads only; a write that may have reached Commerce Layer fails with its next step. The one exception is `sandbox create`: it sends a fresh `Idempotency-Key`, and after a 5xx or a lost connection it repeats the create with the same key, so the API answers the sandbox it made and its token is stored. Each retry waits as long as the API says (`retry_after` seconds in the error, or the `Retry-After` header), at most 30 seconds; without it, 1 then 3 seconds. When the API asks for longer than 30 seconds, the CLI does not wait: the error goes to the agent, with `retry_after`, to plan around. Each retry writes a line to stderr.
|
|
240
113
|
|
|
241
|
-
|
|
242
|
-
- [Universal Commerce Protocol](https://ucp.dev): the protocol every store speaks.
|
|
243
|
-
- [`@commercelayer/cli`](https://www.npmjs.com/package/@commercelayer/cli): the Commerce Layer CLI for humans.
|
|
244
|
-
- [Commerce Layer](https://commercelayer.io): the commerce API behind the stores.
|
|
114
|
+
## Environment
|
|
245
115
|
|
|
246
|
-
|
|
116
|
+
| Variable | What it does |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `C11R_API_URL` | The API's base URL, `https://api.commercelayer.ai/v1` by default; `http://localhost:8787/v1` for wrangler dev. A URL without `/v1` gets it. |
|
|
119
|
+
| `C11R_ORG` | The organization, when `--org` is not given. |
|
|
120
|
+
| `C11R_TOKEN` | An organization token to use as is, whatever is stored: for CI. |
|
|
121
|
+
| `C11R_PROVISIONING_TOKEN` | A ready login token, used as is: for CI. Never a client secret. |
|
|
122
|
+
| `C11R_AUTH_URL` | Commerce Layer's token endpoint, `https://auth.commercelayer.io/oauth/token` by default: for tests. |
|
|
123
|
+
| `C11R_TEST` | `1` marks a sandbox as a test organization, like a name starting `zztest`. |
|
|
124
|
+
| `C11R_SCHEMA_CACHE` | `0` turns off the 10 minute schema cache in `~/.cache/c11r`. |
|
|
125
|
+
| `C11R_RETRY_CAP_S` | The longest the CLI waits before one retry, 30 by default. |
|
|
126
|
+
| `C11R_TIMEOUT_MS` | How long a request may take, 30000 by default (sandbox create waits longer). |
|