@commercelayer/agent-cli 1.0.0-dev.16 → 1.0.0-dev.18
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 -0
- package/dist/builtins/extensions.js +4 -3
- package/dist/builtins/loops.js +6 -6
- package/dist/engine.js +15 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ Destructive verbs (deletes, `orders cancel`, `payments void` and `refund`, `sand
|
|
|
76
76
|
|
|
77
77
|
- Without a login, nothing is checked on this machine: the request goes without a token, and the API's answer (`login_required`, or `state_required` for an organization not claimed) comes through as it is. So do `live_unavailable` (a plan without live), `forbidden` (the login is not an active admin of it) and `not_supported` for a verb that comes with a later step of go-live.
|
|
78
78
|
- A verb that never runs live (sandbox, plans, sales calls, `auth login`, `auth switch`, `shop`) stops on this machine with `state_required`.
|
|
79
|
+
- `c11r events writes --live --org <slug>` (`--since 2026-11-12T00:00:00Z`, an ISO time, `--noun`, `--cursor`, `--limit`) is the audit of live writes, and runs only in live: without `--live` it is `missing_value` with the full command, before any call.
|
|
79
80
|
- `c11r status --live --org <slug>` is the organization's live status, with `live_credential` and the warning `live_credential_held` while the API holds one.
|
|
80
81
|
- `c11r auth logout --live --org <slug>` revokes that credential in Commerce Layer (`DELETE /v1/auth/live`): `{ org, revoked, credential }`. The login stays on this machine. `--dry-run` says what it would revoke.
|
|
81
82
|
- A verb that needs a yes is confirmed by the API in live, with a sentence naming the action, the amount and the mode (`Refunds EUR 1.00 on order N, in LIVE mode, real money.`): the CLI sends it without `confirm=true` and shows that `confirmation_required` as it is, with the API's next command, or asks it with those words in a terminal. `--yes` sends `confirm=true` on every live write, `payments capture` included.
|
|
@@ -32,9 +32,10 @@ export const organizations = {
|
|
|
32
32
|
const req = resourceRequest('organizations', run.verb, run.parsed.id, run.parsed.values);
|
|
33
33
|
// Ending every other token of the organization is confirmed: by the schema's confirm_if, or
|
|
34
34
|
// here for a schema without it.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// With confirm_if the engine has it confirmed, by the human here or by the API's 428 with its
|
|
36
|
+
// sentence; only a schema without it is confirmed here, and then sends confirm=true itself.
|
|
37
|
+
if (run.verb.name === 'connect' && run.parsed.values.revoke_others && !run.globals.dryRun && !run.verb.confirmIf.includes('revoke_others')) {
|
|
38
|
+
await confirm(`Connect ${run.parsed.id} and end every other organization token of it`, run.line, { yes: run.globals.yes, prompter: run.prompter ?? terminal });
|
|
38
39
|
req.confirm = true;
|
|
39
40
|
req.body = { ...req.body, confirm: true };
|
|
40
41
|
}
|
package/dist/builtins/loops.js
CHANGED
|
@@ -33,12 +33,12 @@ export const loops = {
|
|
|
33
33
|
description: 'Loops: a skill on a schedule, under a policy written in advance. Definitions are in loops.yml in the project folder; each pass is one c11r loops run.',
|
|
34
34
|
id: { accepts: ['name'], example: 'orders-15m' },
|
|
35
35
|
verbs: [
|
|
36
|
-
{ verb: 'list', method: 'GET', state: 'none', effect: 'Lists the loops of loops.yml.' },
|
|
37
|
-
{ verb: 'get', method: 'GET', path: '/v1/loops/{id}', state: 'none', effect: 'Returns a loop from loops.yml, with its last pass when the organization has run it.' },
|
|
38
|
-
{ verb: 'create', method: 'POST', path: '/v1/loops/{id}', state: 'none', body: policyFlags(true), effect: 'Adds a loop to loops.yml, after checking its policy against the skill noun\'s schema. Nothing runs until a pass.' },
|
|
39
|
-
{ verb: 'update', method: 'PATCH', path: '/v1/loops/{id}', state: 'none', body: policyFlags(false), effect: 'Changes a loop in loops.yml. A flag given replaces what is there: --allow replaces the whole list.' },
|
|
40
|
-
{ verb: 'delete', method: 'DELETE', path: '/v1/loops/{id}', state: 'none', destructive: true, deletes: true, effect: 'Removes a loop from loops.yml, and its cursor from the organization.' },
|
|
41
|
-
{ verb: 'run', method: 'POST', path: '/v1/loops/{id}/run', effect: 'One pass: what is waiting since the last pass, and the actions the policy allows. Answers done, waiting (new: true for what appeared since the last pass) and failed. With --dry-run nothing changes and the last pass time stays.' },
|
|
36
|
+
{ verb: 'list', live: false, method: 'GET', state: 'none', effect: 'Lists the loops of loops.yml.' },
|
|
37
|
+
{ verb: 'get', live: false, method: 'GET', path: '/v1/loops/{id}', state: 'none', effect: 'Returns a loop from loops.yml, with its last pass when the organization has run it.' },
|
|
38
|
+
{ verb: 'create', live: false, method: 'POST', path: '/v1/loops/{id}', state: 'none', body: policyFlags(true), effect: 'Adds a loop to loops.yml, after checking its policy against the skill noun\'s schema. Nothing runs until a pass.' },
|
|
39
|
+
{ verb: 'update', live: false, method: 'PATCH', path: '/v1/loops/{id}', state: 'none', body: policyFlags(false), effect: 'Changes a loop in loops.yml. A flag given replaces what is there: --allow replaces the whole list.' },
|
|
40
|
+
{ verb: 'delete', live: false, method: 'DELETE', path: '/v1/loops/{id}', state: 'none', destructive: true, deletes: true, effect: 'Removes a loop from loops.yml, and its cursor from the organization.' },
|
|
41
|
+
{ verb: 'run', live: false, method: 'POST', path: '/v1/loops/{id}/run', effect: 'One pass: what is waiting since the last pass, and the actions the policy allows. Answers done, waiting (new: true for what appeared since the last pass) and failed. With --dry-run nothing changes and the last pass time stays.' },
|
|
42
42
|
],
|
|
43
43
|
}),
|
|
44
44
|
async run(run) {
|
package/dist/engine.js
CHANGED
|
@@ -42,6 +42,8 @@ function refusedBeforeEffect(e) {
|
|
|
42
42
|
return status >= 400 && status < 500;
|
|
43
43
|
return e.code === 'unknown_field' || e.code === 'unknown_verb';
|
|
44
44
|
}
|
|
45
|
+
/** Verbs that only run in live mode. */
|
|
46
|
+
const LIVE_ONLY = new Set(['events writes']);
|
|
45
47
|
async function executeOnce(argv, opts) {
|
|
46
48
|
const cmd = parseCommand(argv);
|
|
47
49
|
const builtins = opts.builtins ?? {};
|
|
@@ -87,6 +89,12 @@ async function executeOnce(argv, opts) {
|
|
|
87
89
|
if (live && !verb.live) {
|
|
88
90
|
fail('state_required', `${named} does not run in live mode.`, { required_state: 'claimed', path: 'live', next_step: fixWith(line.replace(/\s--live\b/, '')) });
|
|
89
91
|
}
|
|
92
|
+
// A verb that only reads live data (events writes, the audit of live writes): without --live the
|
|
93
|
+
// API answers the login a bare 401, so the command says what it needs first.
|
|
94
|
+
if (!live && LIVE_ONLY.has(`${schema.noun} ${verb.name}`)) {
|
|
95
|
+
const org = cmd.globals.org ? '' : ' --org ORG_SLUG';
|
|
96
|
+
fail('missing_value', `${named} reads live data only: it needs --live and the organization, named with --org.`, { path: 'live', next_step: fixWith(`${line}${org} --live`) });
|
|
97
|
+
}
|
|
90
98
|
// Live mode acts on an organization named on the command, never the folder's link or the
|
|
91
99
|
// default, which are a sandbox's: --org, or C11R_ORG.
|
|
92
100
|
const liveOrg = live ? (cmd.globals.org ?? (process.env.C11R_ORG || null)) : null;
|
|
@@ -122,7 +130,10 @@ async function executeOnce(argv, opts) {
|
|
|
122
130
|
// amount and the mode ("Refunds EUR 1.00 on order N, in LIVE mode, real money."): the CLI sends
|
|
123
131
|
// without confirm=true and shows that 428 as it is, or asks it in a terminal. --yes sends
|
|
124
132
|
// confirm=true on every live write, payments capture included.
|
|
125
|
-
|
|
133
|
+
// So does a login verb (organizations disconnect), whose sentence names what stops answering.
|
|
134
|
+
const apiAsks = (live || verb.credential === 'login') && (verb.confirm || confirmFlags.length > 0);
|
|
135
|
+
if (apiAsks && cmd.globals.yes && !cmd.globals.dryRun)
|
|
136
|
+
confirmed = true;
|
|
126
137
|
if (live && cmd.globals.yes && verb.method !== 'GET')
|
|
127
138
|
confirmed = true;
|
|
128
139
|
if ((verb.destructive || verb.confirm || confirmFlags.length) && !cmd.globals.dryRun && !apiAsks) {
|
|
@@ -156,10 +167,11 @@ async function executeOnce(argv, opts) {
|
|
|
156
167
|
}
|
|
157
168
|
catch (e) {
|
|
158
169
|
// The API's own confirmation, in live: as it is with no terminal; asked with its words in one.
|
|
159
|
-
if (
|
|
170
|
+
if (apiAsks && !confirmed && e instanceof CliError && e.code === 'confirmation_required') {
|
|
160
171
|
const p = opts.prompter ?? terminal;
|
|
172
|
+
// Without a command of the API's, the next step is this one again, without --yes.
|
|
161
173
|
if (!p.interactive)
|
|
162
|
-
throw e;
|
|
174
|
+
throw e.body.next_step?.command ? e : new CliError({ ...e.body, next_step: { action: 'fix_request', command: line, url: null } }, e.kind, e.state);
|
|
163
175
|
await confirm(e.body.message, line, { yes: false, prompter: p, sentence: e.body.message });
|
|
164
176
|
confirmed = true;
|
|
165
177
|
return sendOnce(req);
|
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.18",
|
|
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": [
|