@commercelayer/agent-cli 1.0.0-dev.1 → 1.0.0-dev.2

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
@@ -14,7 +14,7 @@ This is the 1.0.0 line, in development on the `clean-slate` branch. It replaces
14
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
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
- - 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.
17
+ - 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 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
18
  - Amounts are in major units: `--amount 12.50`.
19
19
  - 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.
20
20
  - A document field the schema marks `file_input` (price rules, rule filters) also reads from a JSON file: `--file <file>`, or `--<field>-file` when a verb has more than one.
package/dist/argv.js CHANGED
@@ -104,7 +104,7 @@ export function parseFields(noun, verb, rest) {
104
104
  for (const k of sub.slice(0, -1))
105
105
  target = (target[k] ??= {});
106
106
  const part = sub.join('.');
107
- target[sub[sub.length - 1]] = part in (spec.properties ?? {}) ? coerce(key, { type: spec.properties[part] }, raw) : raw;
107
+ target[sub[sub.length - 1]] = part in (spec.properties ?? {}) ? coerce(key, spec.properties[part], raw) : raw;
108
108
  continue;
109
109
  }
110
110
  const value = coerce(key, spec, raw);
@@ -122,6 +122,10 @@ function secretAsValue(cmd, key) {
122
122
  /** A value as its flag's type says, or invalid_value with the reason. */
123
123
  export function coerce(key, spec, raw) {
124
124
  const invalid = (why) => fail('invalid_value', `${toFlag(key)} ${JSON.stringify(raw)} ${why}.`, { path: key, ...(spec.values ? { details: { values: spec.values } } : {}) });
125
+ // The schema's fault, not the command's: said so before any value is checked against nothing.
126
+ if ((spec.type === 'enum' || (spec.type === 'array' && spec.items === 'enum')) && !spec.values?.length) {
127
+ fail('invalid_response', `The API's schema gives ${toFlag(key)} as an enum with no values, so c11r cannot check ${JSON.stringify(raw)}. It was not sent.`, { path: key, next_step: { action: 'none' } });
128
+ }
125
129
  switch (spec.type) {
126
130
  case 'string':
127
131
  case 'ref':
@@ -156,7 +160,7 @@ export function coerce(key, spec, raw) {
156
160
  const v = raw.trim().startsWith('{') ? json(raw, invalid) : pairs(key, raw, spec.properties ?? {}, invalid);
157
161
  if (!v || typeof v !== 'object' || Array.isArray(v))
158
162
  invalid('is not a JSON object');
159
- return v;
163
+ return enumParts(key, spec.properties, v);
160
164
  }
161
165
  case 'array': {
162
166
  const item = spec.items ?? 'string';
@@ -164,11 +168,15 @@ export function coerce(key, spec, raw) {
164
168
  const list = json(raw, invalid);
165
169
  if (!Array.isArray(list))
166
170
  invalid('is not a list');
167
- return list.map((x) => (typeof x === 'string' && item !== 'string' ? coerce(key, { type: item, values: spec.values }, x) : x));
171
+ return list.map((x) => (typeof x === 'string' && item !== 'string' ? coerce(key, { type: item, values: spec.values }, x) : item === 'object' ? enumParts(key, spec.properties, x) : x));
168
172
  }
169
173
  // One object per flag (--items sku=A,quantity=2, repeated), or scalars separated by commas.
170
174
  if (item === 'object')
171
175
  return [coerce(key, { type: 'object', properties: spec.properties }, raw)];
176
+ // An in filter on names or text takes the value whole: a name may have a comma ("UK VAT, manual").
177
+ // Several are the flag repeated, or a JSON array. Enums, numbers and dates still split on commas.
178
+ if (spec.filter?.op === 'in' && (item === 'string' || item === 'ref'))
179
+ return [raw];
172
180
  return raw
173
181
  .split(',')
174
182
  .map((s) => s.trim())
@@ -177,6 +185,17 @@ export function coerce(key, spec, raw) {
177
185
  }
178
186
  }
179
187
  }
188
+ /** In JSON too, an enum part of an object is one of its values. */
189
+ function enumParts(key, props, v) {
190
+ if (!v || typeof v !== 'object')
191
+ return v;
192
+ for (const [name, p] of Object.entries(props ?? {})) {
193
+ const part = v[name];
194
+ if (p.type === 'enum' && typeof part === 'string')
195
+ coerce(`${key}.${name}`, p, part);
196
+ }
197
+ return v;
198
+ }
180
199
  function json(raw, invalid) {
181
200
  try {
182
201
  return JSON.parse(raw);
@@ -197,7 +216,7 @@ function pairs(key, raw, props, invalid) {
197
216
  invalid('is not JSON nor key=value pairs separated by commas');
198
217
  const name = part.slice(0, eq).trim();
199
218
  const value = part.slice(eq + 1).trim();
200
- out[name] = props[name] ? coerce(`${key}.${name}`, { type: props[name] }, value) : value;
219
+ out[name] = props[name] ? coerce(`${key}.${name}`, props[name], value) : value;
201
220
  }
202
221
  return out;
203
222
  }
package/dist/help.js CHANGED
@@ -29,7 +29,7 @@ export function indexHelp(builtins, api) {
29
29
  description: 'c11r is the Commerce Layer CLI for coding agents. Every answer, errors included, is one JSON object on stdout.',
30
30
  nouns: [...builtins, ...(api ?? [])].sort((a, b) => (a.noun < b.noun ? -1 : 1)),
31
31
  standard_verbs: 'list, get, create, update, delete. Nouns with a state machine add their own verbs.',
32
- flags: 'Fields in kebab or snake case (--price-list, --price_list). On list, --<field> <value> filters by equality and --<field>.<op> <value> by another operator (--created-at.gte 2026-11-01; in takes values separated by commas). A dotted flag sets part of an object (--customer.email). An array takes a JSON array, values separated by commas, or the flag repeated; an array of objects takes key=value pairs (--items sku=COFFEE,quantity=2). --data takes the fields as one JSON object, or @ and a file name.',
32
+ flags: 'Fields in kebab or snake case (--price-list, --price_list). On list, --<field> <value> filters by equality and --<field>.<op> <value> by another operator (--created-at.gte 2026-11-01). in 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, and a list that has one is sent as a JSON array. A dotted flag sets part of an object (--customer.email). Any other array takes a JSON array, values separated by commas, or the flag repeated; an array of objects takes key=value pairs (--items sku=COFFEE,quantity=2). --data takes the fields as one JSON object, or @ and a file name.',
33
33
  global_flags: GLOBAL_FLAG_HELP,
34
34
  exit_codes: EXIT_CODE_HELP,
35
35
  more: 'c11r <noun> --help describes a noun and its verbs; c11r <noun> <verb> --help a verb and its flags; c11r schema <noun> returns the full schema.',
@@ -74,7 +74,7 @@ export function usageOf(noun, v) {
74
74
  const needed = (f) => f.required || (f.input?.kind === 'secret' && f.input.required);
75
75
  const required = Object.entries(v.flags)
76
76
  .filter(([, f]) => needed(f))
77
- .map(([k, f]) => `${toFlag(k)} <${f.input ? 'file' : f.type === 'enum' ? f.values?.join('|') : f.type}>`);
77
+ .map(([k, f]) => `${toFlag(k)} <${f.input ? 'file' : f.type === 'enum' && f.values?.length ? f.values.join('|') : f.type}>`);
78
78
  const optional = Object.entries(v.flags).some(([k, f]) => !needed(f) && !v.idAlternative.includes(k));
79
79
  const id = v.id ? (v.idAlternative.length ? `<id | ${v.idAlternative.map((k) => `${toFlag(k)} <${v.flags[k].type}>`).join(' ')}>` : '<id>') : '';
80
80
  return [`c11r ${noun} ${v.name}`, id, ...required, optional ? '[--field value ...]' : ''].filter(Boolean).join(' ');
package/dist/resources.js CHANGED
@@ -21,7 +21,7 @@ export function resourceRequest(noun, verb, id, values) {
21
21
  else if (flag.to === 'local')
22
22
  continue;
23
23
  else if (flag.to === 'filter')
24
- query[`filter[${flag.filter.field}][${flag.filter.op}]`] = text(value);
24
+ query[`filter[${flag.filter.field}][${flag.filter.op}]`] = flag.filter.op === 'in' ? inList(value) : text(value);
25
25
  else
26
26
  query[key] = text(value);
27
27
  }
@@ -39,4 +39,7 @@ export function resourceRequest(noun, verb, id, values) {
39
39
  req.body = body;
40
40
  return req;
41
41
  }
42
+ // An in list is joined by commas, or sent as a JSON array of strings when a value has a comma of its
43
+ // own ("UK VAT, manual"), so the API keeps each value whole (contract 28.4).
44
+ const inList = (v) => (Array.isArray(v) && v.some((x) => typeof x === 'string' && x.includes(',')) ? JSON.stringify(v.map(String)) : text(v));
42
45
  const text = (v) => (Array.isArray(v) ? v.join(',') : typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v));
package/dist/schema.js CHANGED
@@ -145,7 +145,7 @@ function flag(f, to, required) {
145
145
  to,
146
146
  ...(f.values ? { values: f.values } : {}),
147
147
  ...(f.items ? { items: f.items } : {}),
148
- ...(f.properties?.length ? { properties: Object.fromEntries(f.properties.map((p) => [p.name, p.type])) } : {}),
148
+ ...(f.properties?.length ? { properties: Object.fromEntries(f.properties.map((p) => [p.name, { type: p.type, ...(p.values ? { values: p.values } : {}) }])) } : {}),
149
149
  ...(f.noun ? { noun: f.noun } : {}),
150
150
  ...(required ? { required: true } : {}),
151
151
  ...(f.description ? { description: f.description } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercelayer/agent-cli",
3
- "version": "1.0.0-dev.1",
3
+ "version": "1.0.0-dev.2",
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": [