@commercelayer/agent-cli 1.0.0-dev.1 → 1.0.0-dev.3
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 +2 -2
- package/dist/argv.js +23 -4
- package/dist/builtins/sandbox.js +3 -5
- package/dist/config.js +24 -7
- package/dist/engine.js +7 -6
- package/dist/help.js +2 -2
- package/dist/resources.js +4 -1
- package/dist/schema.js +1 -1
- package/dist/secrets.js +2 -2
- package/package.json +1 -1
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.
|
|
@@ -50,7 +50,7 @@ Never from a flag or the environment. c11r exchanges the pair for a two hour tok
|
|
|
50
50
|
|
|
51
51
|
`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`.
|
|
52
52
|
|
|
53
|
-
`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. Each file records the record it belongs to. A `delete` of a record whose noun answers secrets (`credentials`, `webhooks`) removes the files c11r wrote for it in `~/.config/c11r/secrets/<org>/` (a credential's secret and tokens, a webhook's shared secret), found by the id, name or code given, 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
|
|
53
|
+
`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. Each file records the record it belongs to. A `delete` of a record whose noun answers secrets (`credentials`, `webhooks`) removes the files c11r wrote for it in `~/.config/c11r/secrets/<org>/` (a credential's secret and tokens, a webhook's shared secret), found by the id, name or code given, 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 on the delete of the record whose secret went to `--out` on this machine, and on `sandbox destroy` of an organization that had one. Any other delete has no such warning. 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.
|
|
54
54
|
|
|
55
55
|
## Global flags
|
|
56
56
|
|
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,
|
|
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}`,
|
|
219
|
+
out[name] = props[name] ? coerce(`${key}.${name}`, props[name], value) : value;
|
|
201
220
|
}
|
|
202
221
|
return out;
|
|
203
222
|
}
|
package/dist/builtins/sandbox.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// reset moves both to the new organization; destroy forgets them. The token is printed never,
|
|
5
5
|
// not even by create: only its hint.
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
|
-
import { credentialsFile, defaultOrg,
|
|
7
|
+
import { credentialsFile, defaultOrg, forgetOutWritten, storedLogin, forgetOrg, LINK_FILE, readLink, removeLink, renameOrg, storedOrgs, storeOrg, writeLink, hintOf } from '../config.js';
|
|
8
8
|
import { CliError, fail } from '../errors.js';
|
|
9
9
|
import { apiUrl, envelope, request } from '../http.js';
|
|
10
10
|
import { parseSchema } from '../schema.js';
|
|
@@ -124,8 +124,7 @@ async function destroy(run) {
|
|
|
124
124
|
// here. The sandbox is gone either way, so this machine forgets it too.
|
|
125
125
|
if (!(e instanceof CliError && e.code === 'unauthorized') || run.globals.dryRun || !run.org.org)
|
|
126
126
|
throw e;
|
|
127
|
-
const out =
|
|
128
|
-
markOutWritten(run.org.org, false);
|
|
127
|
+
const out = forgetOutWritten(run.org.org);
|
|
129
128
|
forgetOrg(run.org.org);
|
|
130
129
|
const link = removeLink(run.org.org);
|
|
131
130
|
const secrets = removeOrgSecrets(run.org.org);
|
|
@@ -136,8 +135,7 @@ async function destroy(run) {
|
|
|
136
135
|
const org = r.data?.org ?? run.org.org;
|
|
137
136
|
if (!org)
|
|
138
137
|
return r;
|
|
139
|
-
const out =
|
|
140
|
-
markOutWritten(org, false);
|
|
138
|
+
const out = forgetOutWritten(org);
|
|
141
139
|
forgetOrg(org);
|
|
142
140
|
const link = removeLink(org);
|
|
143
141
|
const secrets = removeOrgSecrets(org);
|
package/dist/config.js
CHANGED
|
@@ -110,20 +110,37 @@ export function setDefaultOrg(org) {
|
|
|
110
110
|
delete c.default_org;
|
|
111
111
|
writeCredentials(c);
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
/** A record's secret went to an --out file: remembered so its delete can say that file stays. */
|
|
114
|
+
export function markOutWritten(org, record) {
|
|
115
|
+
const keys = record.keys.filter(Boolean);
|
|
116
|
+
if (!keys.length)
|
|
117
|
+
return;
|
|
118
|
+
const c = readCredentials();
|
|
119
|
+
const all = { ...(c.out_written ?? {}) };
|
|
120
|
+
const list = (all[org] ?? []).filter((r) => !(r.noun === record.noun && r.keys.some((k) => keys.includes(k))));
|
|
121
|
+
all[org] = [...list, { noun: record.noun, keys }];
|
|
122
|
+
c.out_written = all;
|
|
123
|
+
writeCredentials(c);
|
|
115
124
|
}
|
|
116
|
-
|
|
125
|
+
/**
|
|
126
|
+
* After a delete: whether that record had a secret written with --out, which is then forgotten.
|
|
127
|
+
* With no record named, the organization goes (sandbox destroy).
|
|
128
|
+
*/
|
|
129
|
+
export function forgetOutWritten(org, record) {
|
|
117
130
|
const c = readCredentials();
|
|
131
|
+
const list = c.out_written?.[org] ?? [];
|
|
132
|
+
const hit = (r) => !record || (r.noun === record.noun && r.keys.some((k) => record.keys.includes(k)));
|
|
133
|
+
if (!list.some(hit))
|
|
134
|
+
return false;
|
|
135
|
+
const rest = list.filter((r) => !hit(r));
|
|
118
136
|
const all = { ...(c.out_written ?? {}) };
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
if (written)
|
|
122
|
-
all[org] = true;
|
|
137
|
+
if (rest.length)
|
|
138
|
+
all[org] = rest;
|
|
123
139
|
else
|
|
124
140
|
delete all[org];
|
|
125
141
|
c.out_written = all;
|
|
126
142
|
writeCredentials(c);
|
|
143
|
+
return true;
|
|
127
144
|
}
|
|
128
145
|
export function storedBooking(id) {
|
|
129
146
|
const b = readCredentials().bookings?.[id];
|
package/dist/engine.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// destructive verb, then the request. A plain noun needs no code here: its schema says
|
|
5
5
|
// everything. Every answer is the API's envelope, { data, state, warnings, next }.
|
|
6
6
|
import { parseCommand, parseFields, toFlag } from './argv.js';
|
|
7
|
-
import {
|
|
7
|
+
import { forgetOutWritten, raiseProblem, resolveOrg } from './config.js';
|
|
8
8
|
import { confirm, terminal } from './confirm.js';
|
|
9
9
|
import { CliError, closest, fail, fixWith } from './errors.js';
|
|
10
10
|
import { indexHelp, nounHelp, verbHelp } from './help.js';
|
|
@@ -118,9 +118,9 @@ export async function execute(argv, opts = {}) {
|
|
|
118
118
|
return filesGone(secretOf(r, run, secretPlan), run);
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
|
-
* A delete
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* A delete on a noun that answers secrets: the files c11r wrote for that record in the secrets
|
|
122
|
+
* folder go with it, listed in removed_files. out_files_not_tracked only when that record's secret
|
|
123
|
+
* was written with --out on this machine: that file stays where it was written.
|
|
124
124
|
*/
|
|
125
125
|
function filesGone(r, run) {
|
|
126
126
|
if (run.verb.name !== 'delete' || run.globals.dryRun || !run.org.org)
|
|
@@ -128,8 +128,9 @@ function filesGone(r, run) {
|
|
|
128
128
|
if (!Object.values(run.schema.verbs).some((v) => v.secret))
|
|
129
129
|
return r;
|
|
130
130
|
const data = (r.data && typeof r.data === 'object' ? r.data : {});
|
|
131
|
-
const
|
|
132
|
-
const
|
|
131
|
+
const keys = [run.parsed.id, data.id, data.name, data.code].filter((k) => typeof k === 'string' && k !== '');
|
|
132
|
+
const removed = removeRecordFiles(run.org.org, run.schema.noun, keys);
|
|
133
|
+
const warn = forgetOutWritten(run.org.org, { noun: run.schema.noun, keys });
|
|
133
134
|
return { ...r, data: { ...data, removed_files: removed }, warnings: [...r.warnings, ...(warn ? [OUT_NOT_TRACKED] : [])] };
|
|
134
135
|
}
|
|
135
136
|
/** A secret in the answer goes to its file; a dry run says where it would go. */
|
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
|
|
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
|
|
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/dist/secrets.js
CHANGED
|
@@ -97,9 +97,9 @@ export function writeSecret(r, plan, ctx) {
|
|
|
97
97
|
const created = ctx.verb === 'create';
|
|
98
98
|
fail('secret_not_written', `The API answered the ${secret.kind ?? 'secret'} of credential ${id} once, and c11r could not write it to ${file}: ${e.message}. It was not printed, and the API cannot show it again. ${created ? `c11r credentials token ${id} gives a working token now; for a new secret, create another credential and delete ${id}.` : `c11r credentials token ${id} gives another token.`}`, { path: 'out', next_step: { action: 'fix_request', command: `c11r credentials token ${id}` }, details: { credential_id: credentialId ?? null, file } });
|
|
99
99
|
}
|
|
100
|
-
// An --out file is not removed with its
|
|
100
|
+
// An --out file is not removed with its record: remember which record has one, for the warning.
|
|
101
101
|
if (plan.fixed && ctx.org)
|
|
102
|
-
markOutWritten(ctx.org,
|
|
102
|
+
markOutWritten(ctx.org, { noun: ctx.noun, keys: [record.id, record.name, record.code, ctx.id].filter((k) => typeof k === 'string' && k !== '') });
|
|
103
103
|
// The API says the secret is shown once: c11r kept it in the file, and printed none of it.
|
|
104
104
|
const warnings = r.warnings.filter((w) => w.code !== 'secret_shown_once' && w.code !== 'token_shown_once');
|
|
105
105
|
if (!ignoredByGit(file))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commercelayer/agent-cli",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.3",
|
|
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": [
|