@commercelayer/agent-cli 0.1.9 → 0.1.10
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 +1 -1
- package/dist/api/stub/management.js +6 -0
- package/dist/commands.js +25 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npx -y @commercelayer/agent-cli@latest init --name "The Blue Brand" # runs c11
|
|
|
15
15
|
| --- | --- |
|
|
16
16
|
| `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`. Reuses the store if they exist. |
|
|
17
17
|
| `plan` | Validate `store.yml` and the product files, and list what `apply` would create, update or delete: markets, products, and the store's name and policies. |
|
|
18
|
-
| `apply [--allow-delete] [--full]` | Make the store match the files. 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; `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. |
|
|
18
|
+
| `apply [--allow-delete] [--full]` | Make the store match the files. 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. |
|
|
19
19
|
| `catalog pull` | Read the products from the catalog source in `store.yml` (a Supabase table, a Google Sheet or a Google Merchant Center feed) into `.commercelayer/products/catalog.csv`. Then `plan` and `apply` as usual. |
|
|
20
20
|
| `storefront init [--force]` | Write `storefront/index.html`: a static page on [drop-in.js](https://commercelayer.github.io/drop-in.js/) listing the products in `store.yml`, grouped by top-level category with a navigation, with a minicart that slides in on add to cart, and the hosted cart and checkout, wired to the sandbox's sales channel. Refuses to overwrite without the flag. |
|
|
21
21
|
| `storefront publish` | Upload the folder at `storefront.path` (default `storefront`) to the store host. |
|
|
@@ -11,6 +11,8 @@ function publicState(r) {
|
|
|
11
11
|
}
|
|
12
12
|
/** Like Commerce Layer refusing a sku in an import. Only the stub does this, so tests can exercise apply_failed. */
|
|
13
13
|
const REFUSED = /^REFUSED-/;
|
|
14
|
+
/** C11R_STUB_BUSY_READS=n: the first n reads of a running import answer 503 commercelayer_busy, as when Commerce Layer rate-limits. Per process. */
|
|
15
|
+
let busyReads = Number(process.env.C11R_STUB_BUSY_READS) || 0;
|
|
14
16
|
/** Items an import gets through per read: small, so the CLI polls a few times. */
|
|
15
17
|
const ITEMS_PER_READ = 2;
|
|
16
18
|
/**
|
|
@@ -83,6 +85,10 @@ export function handleManagement(req) {
|
|
|
83
85
|
return error(410, 'expired', `Sandbox ${org} expired on ${record.expires_at}.`, 'Remove .commercelayer/credentials and run `c11r init` to create a new sandbox; store.yml is kept.');
|
|
84
86
|
}
|
|
85
87
|
if (req.method === 'GET' && rest === '') {
|
|
88
|
+
if (record.apply?.status === 'in_progress' && busyReads > 0) {
|
|
89
|
+
busyReads--;
|
|
90
|
+
return error(503, 'commercelayer_busy', 'Commerce Layer is rate-limiting this organization.', 'Wait a minute and try again; never edit store.yml for this.');
|
|
91
|
+
}
|
|
86
92
|
advance(record);
|
|
87
93
|
writeDb(db);
|
|
88
94
|
return json(200, publicState(record));
|
package/dist/commands.js
CHANGED
|
@@ -18,7 +18,7 @@ export const VERSION = createRequire(import.meta.url)('../package.json').version
|
|
|
18
18
|
export const HELP = {
|
|
19
19
|
init: 'Create a sandbox store for this repo, with no login: c11r init --name "<business name>" (the folder name when --name is absent). The name becomes the organization\'s name, and the org slug and store hostname derive from it. Writes .commercelayer/store.yml and .commercelayer/credentials. Reuses the store if store.yml already exists.',
|
|
20
20
|
plan: 'Show what apply would create, update or delete: markets, products and the store\'s name and policies, from .commercelayer/store.yml and the product files under .commercelayer/products/ (*.yml lists, *.csv rows, merged by sku).',
|
|
21
|
-
apply: 'Make the store match .commercelayer/store.yml and .commercelayer/products/. Refuses deletes unless --allow-delete is given. Product changes go through a Commerce Layer import: apply waits for it (progress as JSON lines on stderr) and prints one result; if it stops with apply_pending the import keeps going, so run `c11r status` to follow it. An apply still running is finished first. --full re-imports every product and price, after edits in the Commerce Layer dashboard.',
|
|
21
|
+
apply: 'Make the store match .commercelayer/store.yml and .commercelayer/products/. Refuses deletes unless --allow-delete is given. Product changes go through a Commerce Layer import: apply waits for it (progress as JSON lines on stderr) and prints one result; if it stops with apply_pending the import keeps going, so run `c11r status` to follow it. While Commerce Layer is busy or unavailable it keeps reading, with a retrying line. An apply still running is finished first. --full re-imports every product and price, after edits in the Commerce Layer dashboard.',
|
|
22
22
|
'catalog pull': 'Read the products from the catalog source in store.yml (catalog: { source: supabase | sheets | merchant, url, table }) and write them to .commercelayer/products/catalog.csv; then plan and apply as usual. Columns: sku, name, description, image_url, category and one price column per market currency, in lowercase (eur, usd). Supabase reads C11R_SUPABASE_URL and C11R_SUPABASE_KEY (the anon key) from .commercelayer/credentials or the environment; a Google Sheet needs its CSV link or a link anyone can open; a Merchant Center feed is RSS/XML or TSV.',
|
|
23
23
|
'storefront init': 'Write storefront/index.html: a static page on drop-in.js listing the products in store.yml, with a minicart that opens on add to cart, and the hosted cart and checkout. Refuses to overwrite unless --force is given.',
|
|
24
24
|
'storefront publish': 'Upload the storefront folder (store.yml storefront.path, default storefront) to the sandbox preview URL.',
|
|
@@ -29,9 +29,15 @@ export const HELP = {
|
|
|
29
29
|
'ucp check': 'Walk every UCP capability the store advertises and report what passes. Places and pays one real test order on the way. Exits 1 when a check fails.',
|
|
30
30
|
status: 'Show the sandbox, what is applied, and whether the store files have changes not yet applied (the same changes plan lists).',
|
|
31
31
|
claim: 'Start keeping this sandbox store: returns a URL and a code for the human to approve in the browser.',
|
|
32
|
-
help: 'Describe the commands. `c11r help <command>` for one command.',
|
|
32
|
+
help: 'Describe the commands. `c11r help <command>` for one command, `c11r help <group>` (catalog, orders, storefront, ucp) for a group.',
|
|
33
33
|
};
|
|
34
34
|
export function help(name) {
|
|
35
|
+
if (name && !HELP[name]) {
|
|
36
|
+
// A group (catalog, orders, storefront, ucp): its commands.
|
|
37
|
+
const group = Object.entries(HELP).filter(([k]) => k.startsWith(`${name} `));
|
|
38
|
+
if (group.length)
|
|
39
|
+
return { group: name, commands: Object.fromEntries(group) };
|
|
40
|
+
}
|
|
35
41
|
if (name) {
|
|
36
42
|
if (!HELP[name])
|
|
37
43
|
fail('unknown_command', `There is no command "${name}".`, `Commands: ${Object.keys(HELP).join(', ')}.`);
|
|
@@ -80,12 +86,17 @@ export async function plan() {
|
|
|
80
86
|
/** How often apply reads a running import, and when it stops waiting. Env overrides are for tests. */
|
|
81
87
|
const POLL_MS = Number(process.env.C11R_APPLY_POLL_MS) || 2000;
|
|
82
88
|
const WAIT_MS = Number(process.env.C11R_APPLY_WAIT_MS) || 10 * 60 * 1000;
|
|
89
|
+
/** Errors that say nothing about the import itself: apply keeps polling through them. */
|
|
90
|
+
const TRANSIENT = new Set(['commercelayer_busy', 'commercelayer_unavailable', 'api_error', 'api_unreachable']);
|
|
91
|
+
/** The longest wait between reads while Commerce Layer answers busy (429). */
|
|
92
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
83
93
|
const jobSummary = (j) => ({ id: j.id, status: j.status, step: j.step, processed: j.processed, total: j.total });
|
|
84
94
|
/** Read the store until its apply job is no longer in progress, with a progress line per change. */
|
|
85
95
|
async function follow(api, state) {
|
|
86
96
|
const until = Date.now() + WAIT_MS;
|
|
87
97
|
let s = state;
|
|
88
98
|
let last = '';
|
|
99
|
+
let wait = POLL_MS;
|
|
89
100
|
while (s.apply?.status === 'in_progress') {
|
|
90
101
|
const line = JSON.stringify(jobSummary(s.apply));
|
|
91
102
|
if (line !== last)
|
|
@@ -95,8 +106,18 @@ async function follow(api, state) {
|
|
|
95
106
|
const j = s.apply;
|
|
96
107
|
fail('apply_pending', `The apply is still running: ${j.processed} of ${j.total} items done (step ${j.step}).`, 'The apply keeps going; run `c11r status` to follow it.');
|
|
97
108
|
}
|
|
98
|
-
await new Promise((r) => setTimeout(r,
|
|
99
|
-
|
|
109
|
+
await new Promise((r) => setTimeout(r, Math.min(wait, Math.max(0, until - Date.now()))));
|
|
110
|
+
try {
|
|
111
|
+
s = await api.getStore();
|
|
112
|
+
wait = POLL_MS;
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
// The import runs on regardless: report the error and read again, backing off while Commerce Layer is busy.
|
|
116
|
+
if (!(e instanceof CliError) || !TRANSIENT.has(e.code))
|
|
117
|
+
throw e;
|
|
118
|
+
wait = e.code === 'commercelayer_busy' ? Math.min(MAX_BACKOFF_MS, wait * 2) : POLL_MS;
|
|
119
|
+
progress({ retrying: { code: e.code, message: e.message, next_read_ms: wait } });
|
|
120
|
+
}
|
|
100
121
|
}
|
|
101
122
|
return s;
|
|
102
123
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commercelayer/agent-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "c11r: the Commerce Layer CLI for coding agents. Builds a sandbox store that people and shopping agents can buy from.",
|
|
5
5
|
"homepage": "https://commercelayer.ai",
|
|
6
6
|
"keywords": [
|