@commercelayer/agent-cli 1.0.0-dev.18 → 1.0.0-dev.19

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 CHANGED
@@ -15,7 +15,7 @@ This is the 1.0.0 line, in development on the `clean-slate` branch. It replaces
15
15
  - Flags are the schema's fields, in kebab or snake case: `--price-list` and `--price_list` are the same.
16
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
17
  - A ref flag, or one the schema marks `nullable`, given `null` or an empty value sends `null` and takes it off: `c11r customers update ada@example.com --customer-group null` sends `customer_group: null`, `c11r orders update 1001 --coupon-code null` sends `coupon_code: null`. A ref the schema marks `nullable: false`, and any other field, sends the text.
18
- - A dotted flag sets part of an object: `--customer.email ada@example.com`. An `in` filter takes several values as the flag repeated or a JSON array; on enums, numbers and dates also separated by commas, while a name or text is one value even with a comma in it (`--tax-calculator.in "UK VAT, manual"`), and a list that has one is sent as a JSON array. Any other array takes the flag repeated or a JSON array, and the same rule holds: enums, numbers and dates also separated by commas, a name or text one value per flag (`--payment-settings "Invoice, 60 days"`); 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
+ - A dotted flag sets part of an object: `--customer.email ada@example.com`. An `in` filter takes several values as the flag repeated or a JSON array; on enums, numbers and dates also separated by commas, while a name or text is one value even with a comma in it (`--tax-calculator.in "UK VAT, manual"`), and a list that has one is sent as a JSON array. Any other array takes the flag repeated or a JSON array, and the same rule holds: enums, numbers and dates also separated by commas, a name or text one value per flag (`--payment-settings "Invoice, 60 days"`); an array of objects takes `key=value` pairs, one object per flag: `--items sku=COFFEE,quantity=2 --items sku=TEA,quantity=1`. Each array flag's help has a `repeat` line with its own form. `--data` takes the fields as one JSON object, or `@` and a file name.
19
19
  - Amounts are in major units: `--amount 12.50`.
20
20
  - A secret field the schema marks `secret_input` (provider credentials) has no flag of its own: `--credentials-file <file>` reads it, as JSON or `KEY=value` lines, from a plain file only its owner can read (mode 600). Without the file, a terminal asks for it without echo; with neither, the command stops with `secret_required`. It is refused as a flag value or in `--data`, and never printed.
21
21
  - A field with documented values the API does not enforce (`known`: webhook topics) takes any value. One outside them is sent anyway, with the warning `unknown_value` and `did_you_mean`. Help lists up to 20 of them, and past that says how many and that `c11r schema <noun>` has them all.
@@ -165,7 +165,7 @@ The CLI's own answers (help, loops.yml) have the same shape, with `state` null.
165
165
  - Every copy's sha256 is checked on each run, also when nothing new is in the index. A copy that is not what sync wrote is an edit: it is kept, neither replaced nor removed, and listed in `edited` with a `skill_edited` warning. `--yes`, on the human's yes, replaces or removes it.
166
166
  - `--except <skill>` (repeated) leaves a skill out, and later syncs remember it; `--include <skill>` takes it back.
167
167
  - A file whose name or version is not what the index says is not used (`skill_invalid`); a link off `commercelayer.ai/skills/` is skipped (`skill_skipped`).
168
- - The answer has `added`, `updated` (from and to), `removed`, `edited` and how many are `unchanged`. With `--dry-run` it reads only the index and writes nothing. When the site cannot be reached it is `site_unavailable` (exit 7), and the copies stay as they are.
168
+ - The answer has `added`, `updated` (from and to) and `removed`, each as `{ "count": 3, "skills": [...] }`; past 10 a list is only its `count` unless `--verbose`. `edited` lists every edited copy, always, and `unchanged` is how many are. With `--dry-run` it reads only the index and writes nothing. When the site cannot be reached it is `site_unavailable` (exit 7), and the copies stay as they are.
169
169
 
170
170
  ## Environment
171
171
 
@@ -20,9 +20,9 @@ const EVERY = /^[1-9]\d*(m|h|d)$/;
20
20
  const policyFlags = (required) => [
21
21
  { name: 'skill', type: 'string', required, description: 'The skill the loop runs, as in manage-orders or report-sales: one a noun\'s schema names in loop.skill or loop.skills.' },
22
22
  { name: 'every', type: 'string', required, description: 'How often a pass should run, as in 15m, 2h or 1d. The scheduler reads it; the CLI does not wait.' },
23
- { name: 'allow', type: 'array', description: 'A verb the policy allows, repeated: approve, payments.capture. Only verbs the noun\'s schema lists in loop.verbs. Without it the loop only reports what is waiting.' },
24
- { name: 'max_total', type: 'array', description: 'The largest order total acted on, in major units: 500 for every currency, or EUR=500 per currency, the flag repeated or separated by commas. Above it, the item waits for the human.' },
25
- { name: 'market', type: 'array', description: 'A market code the loop acts in, repeated. Items in other markets wait. Without it, every market.' },
23
+ { name: 'allow', type: 'array', description: 'A verb the policy allows, repeated: approve, payments.capture. Only verbs the noun\'s schema lists in loop.verbs. Without it the loop only reports what is waiting.', repeat: '--allow VERB --allow VERB, one verb per flag, or one JSON array.' },
24
+ { name: 'max_total', type: 'array', description: 'The largest order total acted on, in major units: 500 for every currency, or EUR=500 per currency, the flag repeated or separated by commas. Above it, the item waits for the human.', repeat: '--max-total AMOUNT for every currency, or --max-total CURRENCY=AMOUNT once per currency (also separated by commas: EUR=500,USD=550), or one JSON array.' },
25
+ { name: 'market', type: 'array', description: 'A market code the loop acts in, repeated. Items in other markets wait. Without it, every market.', repeat: '--market CODE --market CODE, one market code per flag, or one JSON array.' },
26
26
  ];
27
27
  export const loops = {
28
28
  schema: parseSchema({
@@ -25,6 +25,8 @@ const START = '<!-- c11r skills: start -->';
25
25
  const END = '<!-- c11r skills: end -->';
26
26
  const NAME = /^[a-z0-9][a-z0-9-]*$/;
27
27
  const TIMEOUT_MS = 30_000;
28
+ /** Up to this many skills added, updated or removed are listed; past it, only the count unless --verbose. */
29
+ const LISTED = 10;
28
30
  export const skills = {
29
31
  schema: parseSchema({
30
32
  noun: 'skills',
@@ -42,8 +44,9 @@ export const skills = {
42
44
  body: [
43
45
  { name: 'except', type: 'array', items: 'string', description: 'A skill not to keep a copy of, repeated. Remembered: later syncs leave it out too.' },
44
46
  { name: 'include', type: 'array', items: 'string', description: 'A skill left out with --except to keep a copy of again, repeated.' },
47
+ { name: 'verbose', type: 'boolean', description: `Lists every skill added, updated and removed. Without it, a list longer than ${LISTED} is only its count.` },
45
48
  ],
46
- effect: 'Brings the copies up to date with the index at commercelayer.ai: fetches only the skills whose version changed, adds new ones, removes the ones the index dropped, and keeps the list in AGENTS.md (creating the file when there is none). A copy edited on this machine is kept as it is; replacing or removing it needs the human\'s yes. Answers added, updated, removed, edited and how many are unchanged. With --dry-run it reads only the index and writes nothing.',
49
+ effect: 'Brings the copies up to date with the index at commercelayer.ai: fetches only the skills whose version changed, adds new ones, removes the ones the index dropped, and keeps the list in AGENTS.md (creating the file when there is none). A copy edited on this machine is kept as it is; replacing or removing it needs the human\'s yes. Answers added, updated and removed as a count with the skills when there are few, every edited copy, and how many are unchanged. With --dry-run it reads only the index and writes nothing.',
47
50
  },
48
51
  ],
49
52
  }),
@@ -188,6 +191,7 @@ async function sync(run) {
188
191
  const warnings = [];
189
192
  const except = v.except ?? [];
190
193
  const include = v.include ?? [];
194
+ const verbose = v.verbose === true;
191
195
  for (const n of [...except, ...include])
192
196
  if (!NAME.test(n))
193
197
  fail('invalid_value', `${n} is not a skill name: lowercase letters, digits and dashes, as in manage-orders.`, { path: except.includes(n) ? 'except' : 'include' });
@@ -270,7 +274,7 @@ async function sync(run) {
270
274
  removed.push({ name: k.name, version: k.version });
271
275
  const agentsNow = readOr(path.join(root, AGENTS_FILE));
272
276
  const agentsNext = withBlock(agentsNow, agentsBlock(wanted, SKILLS_DIR.split(path.sep).join('/')));
273
- return answer({ dry_run: true, dir, index: { url: from, version: indexVersion }, added, updated, removed, edited, unchanged, agents_md: agentsNow === null ? 'created' : agentsNow === agentsNext ? 'unchanged' : 'updated' }, warnings, edited, replaceEdits);
277
+ return answer({ dry_run: true, dir, index: { url: from, version: indexVersion }, added, updated, removed, edited, unchanged, agents_md: agentsNow === null ? 'created' : agentsNow === agentsNext ? 'unchanged' : 'updated' }, warnings, edited, replaceEdits, verbose);
274
278
  }
275
279
  // Fetch first, write after: a cut-off sync writes nothing.
276
280
  if (replaceEdits)
@@ -335,9 +339,13 @@ async function sync(run) {
335
339
  if (agents_md !== 'unchanged')
336
340
  writeAtomic(agentsFile, agentsNext);
337
341
  const stillEdited = replaceEdits ? [] : edited;
338
- return answer({ dir, index: { url: from, version: indexVersion }, added, updated, removed, edited: stillEdited, unchanged, agents_md }, warnings, stillEdited, replaceEdits);
342
+ return answer({ dir, index: { url: from, version: indexVersion }, added, updated, removed, edited: stillEdited, unchanged, agents_md }, warnings, stillEdited, replaceEdits, verbose);
339
343
  }
340
- function answer(data, warnings, edited, replaced) {
344
+ function answer(data, warnings, edited, replaced, verbose) {
345
+ for (const k of ['added', 'updated', 'removed']) {
346
+ const list = data[k];
347
+ data[k] = verbose || list.length <= LISTED ? { count: list.length, skills: list } : { count: list.length };
348
+ }
341
349
  if (!replaced) {
342
350
  for (const e of edited) {
343
351
  warnings.push({ code: 'skill_edited', message: `${e.name} was edited on this machine: kept as it is${e.latest ? `, while the index has ${e.latest}` : ', though the index dropped it'}. Replacing it loses those edits: ask the human first, and on their yes run the sync again with --yes.` });
package/dist/help.js CHANGED
@@ -63,10 +63,33 @@ export function verbHelp(s, v) {
63
63
  }
64
64
  /** Up to this many documented values are listed in help; past it, help says where they are. */
65
65
  const KNOWN_IN_FULL = 20;
66
+ const CAPS = (name) => name.toUpperCase().replace(/-/g, '_');
67
+ /** How an array flag takes several values, built from its items and, for objects, their parts. */
68
+ function repeatOf(key, f) {
69
+ if (f.repeat)
70
+ return f.repeat;
71
+ const flag = toFlag(key);
72
+ const item = f.items ?? 'string';
73
+ if (item === 'object') {
74
+ const parts = Object.entries(f.properties ?? {});
75
+ const required = parts.filter(([, p]) => p.required);
76
+ const shown = required.length ? required : parts;
77
+ const optional = required.length ? parts.filter(([, p]) => !p.required).map(([n]) => n) : [];
78
+ const pairs = shown.length ? shown.map(([n]) => `${n}=${CAPS(n)}`).join(',') : 'KEY=VALUE,KEY=VALUE';
79
+ return `${flag} ${pairs}, the flag once per item, or one JSON array of objects.${optional.length ? ` Optional parts: ${optional.join(', ')}.` : ''}`;
80
+ }
81
+ if (item === 'string' || item === 'ref') {
82
+ const v = item === 'ref' ? 'ID' : 'VALUE';
83
+ return `${flag} ${v} ${flag} ${v}, one value per flag (a comma is part of the value), or one JSON array.`;
84
+ }
85
+ const v = item === 'integer' || item === 'number' || item === 'money' ? 'N' : item === 'date' || item === 'datetime' ? 'DATE' : 'VALUE';
86
+ return `${flag} ${v},${v}, or the flag repeated, or one JSON array.`;
87
+ }
66
88
  function flagHelp(key, f, noun) {
67
89
  return {
68
90
  flag: toFlag(key),
69
91
  type: f.type === 'array' ? `array of ${f.items ?? 'string'}` : f.type === 'ref' ? `${f.noun ?? 'resource'} id` : f.type === 'money' ? 'amount in major units' : f.type,
92
+ ...(f.type === 'array' ? { repeat: repeatOf(key, f) } : {}),
70
93
  ...(f.required || (f.input?.kind === 'secret' && f.input.required) ? { required: true } : {}),
71
94
  ...(f.input ? { reads: f.input.kind === 'secret' ? `${f.input.target}, a secret: never a flag value, never printed` : `${f.input.target}, a JSON document` } : {}),
72
95
  ...(f.values ? { values: f.values } : {}),
package/dist/schema.js CHANGED
@@ -149,11 +149,12 @@ function flag(f, to, required) {
149
149
  ...(f.values ? { values: f.values } : {}),
150
150
  ...(Array.isArray(f.known) && f.known.length ? { known: f.known } : {}),
151
151
  ...(f.items ? { items: f.items } : {}),
152
- ...(f.properties?.length ? { properties: Object.fromEntries(f.properties.map((p) => [p.name, { type: p.type, ...(p.values ? { values: p.values } : {}) }])) } : {}),
152
+ ...(f.properties?.length ? { properties: Object.fromEntries(f.properties.map((p) => [p.name, { type: p.type, ...(p.values ? { values: p.values } : {}), ...(p.required ? { required: true } : {}) }])) } : {}),
153
153
  ...(f.noun ? { noun: f.noun } : {}),
154
154
  ...(f.nullable !== undefined ? { nullable: f.nullable } : {}),
155
155
  ...(required ? { required: true } : {}),
156
156
  ...(f.description ? { description: f.description } : {}),
157
+ ...(typeof f.repeat === 'string' && f.repeat ? { repeat: f.repeat } : {}),
157
158
  };
158
159
  }
159
160
  function bad(noun, why) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercelayer/agent-cli",
3
- "version": "1.0.0-dev.18",
3
+ "version": "1.0.0-dev.19",
4
4
  "description": "c11r: the Commerce Layer CLI for coding agents. One grammar, c11r <noun> <verb>, from a sandbox to a running organization.",
5
5
  "homepage": "https://commercelayer.ai",
6
6
  "keywords": [