@commercelayer/agent-cli 1.0.0-dev.1 → 1.0.0-dev.11
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 +18 -7
- package/dist/argv.js +33 -6
- package/dist/builtins/extensions.js +169 -1
- package/dist/builtins/loops.js +50 -21
- package/dist/builtins/sandbox.js +5 -7
- package/dist/config.js +24 -7
- package/dist/engine.js +56 -13
- package/dist/help.js +7 -4
- package/dist/http.js +10 -0
- package/dist/inputs.js +21 -1
- package/dist/project.js +175 -0
- package/dist/resources.js +4 -1
- package/dist/schema.js +11 -3
- package/dist/schemas.js +82 -8
- package/dist/secrets.js +25 -7
- package/package.json +1 -1
package/dist/secrets.js
CHANGED
|
@@ -14,6 +14,8 @@ import os from 'node:os';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { markOutWritten } from './config.js';
|
|
16
16
|
import { fail } from './errors.js';
|
|
17
|
+
/** Where the record's id goes in a file named after it, once the answer gives the id. */
|
|
18
|
+
const ID_SLOT = '<id>';
|
|
17
19
|
export function secretsDir() {
|
|
18
20
|
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
19
21
|
return path.join(base, 'c11r', 'secrets');
|
|
@@ -23,10 +25,14 @@ const safe = (s) => s.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|
|
|
|
23
25
|
export function planSecret(opts) {
|
|
24
26
|
const out = opts.values.out;
|
|
25
27
|
const format = opts.values.format ?? null;
|
|
26
|
-
|
|
28
|
+
// By the name given, else the id given; a record with neither (a gift card) by the id it is
|
|
29
|
+
// given, so each has a file of its own: gift-card-<id>.env.
|
|
30
|
+
const named = opts.values.name ?? opts.id;
|
|
31
|
+
const byRecord = !out && opts.verb !== 'token' && (named === undefined || named === null || named === '');
|
|
32
|
+
const base = opts.verb === 'token' ? `${safe(opts.id ?? 'credential')}-token` : byRecord ? `${safe(opts.noun.replace(/s$/, ''))}-${ID_SLOT}` : safe(String(named));
|
|
27
33
|
const file = out ? path.resolve(out) : path.join(secretsDir(), safe(opts.org ?? 'default'), base);
|
|
28
|
-
const plan = { file: out || !format ? file : `${file}.${format}`, format, fixed: Boolean(out), overwrite: opts.yes };
|
|
29
|
-
const candidates = plan.fixed || format ? [plan.file] : [`${file}.env`, `${file}.json`];
|
|
34
|
+
const plan = { file: out || !format ? file : `${file}.${format}`, format, fixed: Boolean(out), overwrite: opts.yes, byRecord };
|
|
35
|
+
const candidates = byRecord ? [] : plan.fixed || format ? [plan.file] : [`${file}.env`, `${file}.json`];
|
|
30
36
|
for (const c of candidates) {
|
|
31
37
|
let st = null;
|
|
32
38
|
try {
|
|
@@ -43,7 +49,7 @@ export function planSecret(opts) {
|
|
|
43
49
|
fail('conflict', `${c} exists. c11r replaces it only with --yes, or writes the secret elsewhere with --out.`, { path: 'out', next_step: { action: 'fix_request', command: null }, details: { file: c } }, 'rejected');
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
|
-
const dir = path.dirname(
|
|
52
|
+
const dir = path.dirname(plan.file);
|
|
47
53
|
const existing = nearestExisting(dir);
|
|
48
54
|
try {
|
|
49
55
|
fs.accessSync(existing, fs.constants.W_OK);
|
|
@@ -74,7 +80,7 @@ export function writeSecret(r, plan, ctx) {
|
|
|
74
80
|
const credentialId = str(rec.id) ?? ctx.id;
|
|
75
81
|
const record = { noun: ctx.noun, id: credentialId ?? null, name: str(rec.name), code: str(rec.code) };
|
|
76
82
|
const format = plan.format ?? (secret.format === 'json' ? 'json' : 'env');
|
|
77
|
-
const file = plan.fixed || plan.format ? plan.file : `${plan.file}.${format}
|
|
83
|
+
const file = byRecordFile(plan.fixed || plan.format ? plan.file : `${plan.file}.${format}`, plan, credentialId);
|
|
78
84
|
const env = secret.env && Object.keys(secret.env).length ? { ...secret.env } : { [`CL_${(secret.kind ?? 'secret').toUpperCase()}`]: String(secret.value ?? '') };
|
|
79
85
|
// The endpoint goes with every secret, so the file is enough to call Commerce Layer.
|
|
80
86
|
if (!env.CL_BASE_ENDPOINT && typeof data.base_endpoint === 'string')
|
|
@@ -97,9 +103,9 @@ export function writeSecret(r, plan, ctx) {
|
|
|
97
103
|
const created = ctx.verb === 'create';
|
|
98
104
|
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
105
|
}
|
|
100
|
-
// An --out file is not removed with its
|
|
106
|
+
// An --out file is not removed with its record: remember which record has one, for the warning.
|
|
101
107
|
if (plan.fixed && ctx.org)
|
|
102
|
-
markOutWritten(ctx.org,
|
|
108
|
+
markOutWritten(ctx.org, { noun: ctx.noun, keys: [record.id, record.name, record.code, ctx.id].filter((k) => typeof k === 'string' && k !== '') });
|
|
103
109
|
// The API says the secret is shown once: c11r kept it in the file, and printed none of it.
|
|
104
110
|
const warnings = r.warnings.filter((w) => w.code !== 'secret_shown_once' && w.code !== 'token_shown_once');
|
|
105
111
|
if (!ignoredByGit(file))
|
|
@@ -111,6 +117,18 @@ export function writeSecret(r, plan, ctx) {
|
|
|
111
117
|
warnings,
|
|
112
118
|
};
|
|
113
119
|
}
|
|
120
|
+
/** A file named after its record: the id in its slot, and a file there already left alone. */
|
|
121
|
+
function byRecordFile(file, plan, id) {
|
|
122
|
+
if (!plan.byRecord)
|
|
123
|
+
return file;
|
|
124
|
+
// The id as the API gives it, case kept: ids are case-sensitive (lqgwUkXlBw).
|
|
125
|
+
const slot = (id ?? crypto.randomBytes(4).toString('hex')).replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[.-]+/, '') || crypto.randomBytes(4).toString('hex');
|
|
126
|
+
const named = file.replace(ID_SLOT, slot);
|
|
127
|
+
if (plan.overwrite || !fs.existsSync(named))
|
|
128
|
+
return named;
|
|
129
|
+
const ext = path.extname(named);
|
|
130
|
+
return `${named.slice(0, -ext.length || undefined)}-${crypto.randomBytes(3).toString('hex')}${ext}`;
|
|
131
|
+
}
|
|
114
132
|
/** What a dry run says about the file: where it would go and whether one is there. */
|
|
115
133
|
export function describePlan(plan) {
|
|
116
134
|
const files = plan.fixed || plan.format ? [plan.file] : [`${plan.file}.env`, `${plan.file}.json`];
|
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.11",
|
|
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": [
|