@commercelayer/agent-cli 1.0.0-dev.11 → 1.0.0-dev.12
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 +4 -1
- package/dist/builtins/extensions.js +41 -1
- package/dist/confirm.js +4 -3
- package/dist/engine.js +27 -1
- package/dist/help.js +5 -2
- package/dist/schema.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ This is the 1.0.0 line, in development on the `clean-slate` branch. It replaces
|
|
|
18
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 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.
|
|
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
|
+
- 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.
|
|
21
22
|
- 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. Where the verb takes `--format csv` (`imports create`), the file goes as CSV text with `--format csv`, or when its name ends in `.csv` and no format is given; otherwise it is JSON.
|
|
22
23
|
- A verb whose schema lists `id_alternative` takes the id or those flags in its place: `c11r prices get pr_1`, or `c11r prices get --sku BEANS-1KG --price-list eur`. Both at once, or half the flags, stop before sending.
|
|
23
24
|
|
|
@@ -64,7 +65,7 @@ Never from a flag or the environment. c11r exchanges the pair for a two hour tok
|
|
|
64
65
|
| `--live` | Live data, on a claimed organization: `?mode=live` on every request. Without it, test mode. |
|
|
65
66
|
| `--help` | Describes the noun or verb it follows, from its schema. |
|
|
66
67
|
|
|
67
|
-
Destructive verbs (deletes, `orders cancel`, `payments void` and `refund`, `sandbox reset` and `destroy`), and any verb the schema marks `confirm`, 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.
|
|
68
|
+
Destructive verbs (deletes, `orders cancel`, `payments void` and `refund`, `sandbox reset` and `destroy`), and any verb the schema marks `confirm`, 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. Its `next_step.command` is the same command without `--yes`: the agent asks the human first, and only on their yes runs it again with `--yes`. The API's own `confirmation_required` works the same way.
|
|
68
69
|
|
|
69
70
|
## Which organization
|
|
70
71
|
|
|
@@ -82,6 +83,8 @@ Records that share a key (two shipping methods with the same name in one market,
|
|
|
82
83
|
|
|
83
84
|
After a Configure write that went through (create, update, delete or a domain verb), in a folder whose `store.yml` names the same organization, the answer warns `config_drift` and `.c11r/drift` notes the write; `config plan` repeats the warning, and `config export` clears it.
|
|
84
85
|
|
|
86
|
+
`c11r scaffold drop-in --out <file>` writes the drop-in.js snippet as one HTML file: the part for the page's `<head>`, then the part for its `<body>`, each under a comment saying where it goes. It never replaces a different file without `--yes`. Without `--out`, the snippet stays in the JSON answer.
|
|
87
|
+
|
|
85
88
|
`c11r exports get <id> --out <file>` downloads a completed export while its link lasts (5 minutes), unzipped unless the name ends in `.gz`; `--wait` reads the job again until it completes.
|
|
86
89
|
|
|
87
90
|
## Output, errors and exit codes
|
|
@@ -272,4 +272,44 @@ export const exports = {
|
|
|
272
272
|
return { ...r, data: { ...rest, file, bytes: bytes.length, unzipped: zipped && !file.endsWith('.gz') } };
|
|
273
273
|
},
|
|
274
274
|
};
|
|
275
|
-
|
|
275
|
+
// scaffold (46): drop-in --out writes the snippet as one HTML file, its head part and its body part
|
|
276
|
+
// each under a comment saying where it goes. It never replaces a different file without --yes.
|
|
277
|
+
export const scaffold = {
|
|
278
|
+
flags: {
|
|
279
|
+
'drop-in': {
|
|
280
|
+
out: { type: 'string', to: 'local', description: 'Write the snippet to this HTML file: the part for the page\'s <head>, then the part for its <body>, each under a comment saying where it goes. A different file there is replaced only with --yes.' },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
async run(run) {
|
|
284
|
+
const out = run.parsed.values.out;
|
|
285
|
+
if (run.verb.name !== 'drop-in' || !out)
|
|
286
|
+
return undefined;
|
|
287
|
+
const file = path.resolve(out);
|
|
288
|
+
// Before anything is sent: a different file there stays without --yes.
|
|
289
|
+
const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
290
|
+
const r = await run.send(resourceRequest('scaffold', run.verb, undefined, run.parsed.values));
|
|
291
|
+
if (run.globals.dryRun)
|
|
292
|
+
return r;
|
|
293
|
+
const data = r.data ?? {};
|
|
294
|
+
const snippet = data.snippet;
|
|
295
|
+
if (!snippet || typeof snippet.head !== 'string' || typeof snippet.body !== 'string')
|
|
296
|
+
fail('invalid_response', 'scaffold drop-in answered without snippet.head and snippet.body.', { next_step: { action: 'none' } });
|
|
297
|
+
const html = [
|
|
298
|
+
`<!-- c11r scaffold drop-in${run.org.org ? ` (${run.org.org})` : ''}: this part goes in the page's <head>. -->`,
|
|
299
|
+
snippet.head.trimEnd(),
|
|
300
|
+
'',
|
|
301
|
+
`<!-- This part goes in the page's <body>, where the products show. -->`,
|
|
302
|
+
snippet.body.trimEnd(),
|
|
303
|
+
'',
|
|
304
|
+
].join('\n');
|
|
305
|
+
if (before !== null && before !== html && !run.globals.yes) {
|
|
306
|
+
fail('conflict', `${file} is there already, and differs from this snippet. c11r replaces it only with --yes.`, { path: 'out', details: { files: [file] }, next_step: { action: 'fix_request', command: null } }, 'rejected');
|
|
307
|
+
}
|
|
308
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
309
|
+
if (before !== html)
|
|
310
|
+
fs.writeFileSync(file, html);
|
|
311
|
+
const { snippet: _snippet, ...rest } = data;
|
|
312
|
+
return { ...r, data: { ...rest, file, replaced: before !== null && before !== html } };
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
export const extensions = { organizations, 'sales-calls': salesCalls, config, exports, scaffold };
|
package/dist/confirm.js
CHANGED
|
@@ -45,9 +45,10 @@ export async function confirm(action, command, opts) {
|
|
|
45
45
|
return;
|
|
46
46
|
const p = opts.prompter ?? terminal;
|
|
47
47
|
if (!p.interactive) {
|
|
48
|
-
// The agent asks the human; on a yes it repeats the command with --yes.
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// The agent asks the human; on a yes it repeats the command with --yes. next never carries the
|
|
49
|
+
// --yes itself: only the human's answer adds it.
|
|
50
|
+
fail('confirmation_required', `${action} needs confirmation, and there is no terminal to ask in. Ask the human first; on their yes, run it again with --yes.`, {
|
|
51
|
+
next_step: { action: 'fix_request', command },
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
const answer = (await p.ask(`${action}. Go ahead? [y/N] `)).trim().toLowerCase();
|
package/dist/engine.js
CHANGED
|
@@ -74,6 +74,7 @@ async function executeOnce(argv, opts) {
|
|
|
74
74
|
await readInputs(schema.noun, verb, parsed, null);
|
|
75
75
|
check(schema, verb, parsed);
|
|
76
76
|
await readInputs(schema.noun, verb, parsed, opts.prompter ?? terminal);
|
|
77
|
+
const unknown = unknownValues(verb, parsed.values);
|
|
77
78
|
const line = ['c11r', ...argv.filter((a) => a !== '--yes' && a !== '-y')].map(quote).join(' ');
|
|
78
79
|
const org = resolveOrg(cmd.globals.org);
|
|
79
80
|
const login = hasLogin();
|
|
@@ -143,7 +144,32 @@ async function executeOnce(argv, opts) {
|
|
|
143
144
|
};
|
|
144
145
|
const own = (await builtins[schema.noun]?.run?.(run)) ?? (await ext?.run?.(run));
|
|
145
146
|
const r = own ?? (await run.send(resourceRequest(schema.noun, verb, parsed.id, parsed.values)));
|
|
146
|
-
|
|
147
|
+
const out = drifted(filesGone(secretOf(r, run, secretPlan), run), run);
|
|
148
|
+
return unknown.length ? { ...out, warnings: [...unknown, ...out.warnings] } : out;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A value outside a flag's documented values (known: webhook topics): not enforced, so it is sent,
|
|
152
|
+
* with a warning and the closest documented ones.
|
|
153
|
+
*/
|
|
154
|
+
function unknownValues(verb, values) {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const [key, f] of Object.entries(verb.flags)) {
|
|
157
|
+
const v = values[key];
|
|
158
|
+
if (!f.known || v === undefined || v === null)
|
|
159
|
+
continue;
|
|
160
|
+
for (const x of (Array.isArray(v) ? v : [v]).filter((x) => typeof x === 'string' && x !== '')) {
|
|
161
|
+
if (f.known.includes(x))
|
|
162
|
+
continue;
|
|
163
|
+
const near = closest(x, f.known);
|
|
164
|
+
out.push({
|
|
165
|
+
code: 'unknown_value',
|
|
166
|
+
message: `${x} is not one of the documented values of ${toFlag(key)}${near.length ? ` (closest: ${near.join(', ')})` : ''}; it was sent anyway.`,
|
|
167
|
+
path: key,
|
|
168
|
+
...(near.length ? { did_you_mean: near } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
147
173
|
}
|
|
148
174
|
/**
|
|
149
175
|
* A Configure write that went through, in a folder whose store.yml names this organization: the
|
package/dist/help.js
CHANGED
|
@@ -58,16 +58,19 @@ export function verbHelp(s, v) {
|
|
|
58
58
|
...(v.confirm ? { confirmation: 'Asks for confirmation unless --yes; with no terminal and no --yes it stops with confirmation_required.' } : {}),
|
|
59
59
|
...(v.loopable ? { loopable: 'A loop policy may allow this verb.' } : {}),
|
|
60
60
|
...(v.live ? {} : { live: 'Not in live mode.' }),
|
|
61
|
-
flags: Object.entries(v.flags).map(([key, f]) => flagHelp(key, f)),
|
|
61
|
+
flags: Object.entries(v.flags).map(([key, f]) => flagHelp(key, f, s.noun)),
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
/** Up to this many documented values are listed in help; past it, help says where they are. */
|
|
65
|
+
const KNOWN_IN_FULL = 20;
|
|
66
|
+
function flagHelp(key, f, noun) {
|
|
65
67
|
return {
|
|
66
68
|
flag: toFlag(key),
|
|
67
69
|
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,
|
|
68
70
|
...(f.required || (f.input?.kind === 'secret' && f.input.required) ? { required: true } : {}),
|
|
69
71
|
...(f.input ? { reads: f.input.kind === 'secret' ? `${f.input.target}, a secret: never a flag value, never printed` : `${f.input.target}, a JSON document` } : {}),
|
|
70
72
|
...(f.values ? { values: f.values } : {}),
|
|
73
|
+
...(f.known ? { known: f.known.length <= KNOWN_IN_FULL ? f.known : `one of ${f.known.length} documented values: c11r schema ${noun}` } : {}),
|
|
71
74
|
...(clearable(f) ? { clear: 'null, or an empty value, takes it off' } : {}),
|
|
72
75
|
...(f.filter ? { filter: f.filter.op } : {}),
|
|
73
76
|
...(f.description ? { description: f.description } : {}),
|
package/dist/schema.js
CHANGED
|
@@ -118,7 +118,7 @@ function listFlags(flags, fields, doc) {
|
|
|
118
118
|
if (op === 'in')
|
|
119
119
|
flags[key] = { ...base, type: 'array', items: f.type === 'array' ? f.items : f.type, filter: { field: f.name, op } };
|
|
120
120
|
else if (op === 'null')
|
|
121
|
-
flags[key] = { ...base, type: 'boolean', values: undefined, filter: { field: f.name, op } };
|
|
121
|
+
flags[key] = { ...base, type: 'boolean', values: undefined, known: undefined, filter: { field: f.name, op } };
|
|
122
122
|
else
|
|
123
123
|
flags[key] = { ...base, filter: { field: f.name, op } };
|
|
124
124
|
}
|
|
@@ -147,6 +147,7 @@ function flag(f, to, required) {
|
|
|
147
147
|
type: f.type,
|
|
148
148
|
to,
|
|
149
149
|
...(f.values ? { values: f.values } : {}),
|
|
150
|
+
...(Array.isArray(f.known) && f.known.length ? { known: f.known } : {}),
|
|
150
151
|
...(f.items ? { items: f.items } : {}),
|
|
151
152
|
...(f.properties?.length ? { properties: Object.fromEntries(f.properties.map((p) => [p.name, { type: p.type, ...(p.values ? { values: p.values } : {}) }])) } : {}),
|
|
152
153
|
...(f.noun ? { noun: f.noun } : {}),
|
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.12",
|
|
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": [
|