@zeyos/cli 0.5.0 → 0.6.0
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 +10 -0
- package/bin/zeyos.mjs +150 -8
- package/commands/count.mjs +4 -2
- package/commands/doctor.mjs +8 -1
- package/commands/list.mjs +4 -1
- package/commands/sum.mjs +112 -0
- package/lib/client.mjs +32 -14
- package/lib/command.mjs +128 -0
- package/lib/config.mjs +3 -0
- package/lib/resource-config.mjs +11 -7
- package/lib/resources.mjs +107 -0
- package/lib/types.mjs +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -80,15 +80,25 @@ Inspect dynamic schema definitions:
|
|
|
80
80
|
```bash
|
|
81
81
|
zeyos count customfields --json
|
|
82
82
|
zeyos list customfields --fields ID,name,identifier,context,type --json
|
|
83
|
+
zeyos count dunning --json
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
Inspect actionsteps/time-entry evidence and ticket mail:
|
|
86
87
|
|
|
87
88
|
```bash
|
|
88
89
|
zeyos list actionsteps --fields ID,name,status,date,duedate,effort,ticket,account --json
|
|
90
|
+
zeyos sum actionsteps effort --filter '{"status":[1,3]}' --json
|
|
89
91
|
zeyos list messages --fields ID,date,mailbox,subject,sender_email,to_email,ticket,reference --filter '{"ticket":42}' --json
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
Filters accept common agent-generated shapes and normalize them to the native ZeyOS
|
|
95
|
+
request form. Use `--query --json` to inspect the request without sending it:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
zeyos list tickets --filter '{"status":{"$nin":[8,9,10]},"priority":[3,4]}' --query --json
|
|
99
|
+
zeyos list accounts --filter '{"name__like":"Acme%","ID__in":[1,2,3]}' --query --json
|
|
100
|
+
```
|
|
101
|
+
|
|
92
102
|
Create, update, and delete:
|
|
93
103
|
|
|
94
104
|
```bash
|
package/bin/zeyos.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* whoami Show current user info
|
|
11
11
|
* list <resource> List records
|
|
12
12
|
* count <resource> Count records
|
|
13
|
+
* sum <resource> Sum a numeric field across matching records
|
|
13
14
|
* get <resource> <id> Fetch a single record
|
|
14
15
|
* show <resource> <id> Alias for get
|
|
15
16
|
* create <resource> Create a new record
|
|
@@ -42,6 +43,7 @@ ${_c.bold('Commands:')}
|
|
|
42
43
|
${_c.cyan('whoami')} Show currently authenticated user
|
|
43
44
|
${_c.cyan('list')} <resource> List / query records
|
|
44
45
|
${_c.cyan('count')} <resource> Count records (with optional filter)
|
|
46
|
+
${_c.cyan('sum')} <resource> <field> Sum a numeric field
|
|
45
47
|
${_c.cyan('get')} <resource> <id> Fetch a single record by ID
|
|
46
48
|
${_c.cyan('show')} <resource> <id> Alias for get
|
|
47
49
|
${_c.cyan('create')} <resource> Create a new record
|
|
@@ -68,6 +70,7 @@ ${_c.bold('Examples:')}
|
|
|
68
70
|
${_z} list tickets --filter '{"status":1}' --sort -lastmodified
|
|
69
71
|
${_z} list tickets --filter-file ./filters/open-tickets.json
|
|
70
72
|
${_z} count tickets --filter '{"status":1}'
|
|
73
|
+
${_z} sum actionsteps effort --filter '{"status":[1,3]}'
|
|
71
74
|
${_z} get ticket 42
|
|
72
75
|
${_z} get ticket 42 --all
|
|
73
76
|
${_z} create ticket --name "Fix login bug" --priority 3
|
|
@@ -105,6 +108,7 @@ const OPTIONS = {
|
|
|
105
108
|
'sort': { type: 'string' },
|
|
106
109
|
'limit': { type: 'string' },
|
|
107
110
|
'offset': { type: 'string' },
|
|
111
|
+
'page-size': { type: 'string' },
|
|
108
112
|
'expand': { type: 'string' },
|
|
109
113
|
'extdata': { type: 'boolean' },
|
|
110
114
|
'tags': { type: 'boolean' },
|
|
@@ -127,6 +131,12 @@ const OPTIONS = {
|
|
|
127
131
|
'from-current': { type: 'boolean' },
|
|
128
132
|
};
|
|
129
133
|
|
|
134
|
+
const COMMON_COMMAND_HELP = `\
|
|
135
|
+
Global options:
|
|
136
|
+
--profile <name> Use a named credential profile for this command
|
|
137
|
+
--no-color Disable ANSI colors
|
|
138
|
+
`;
|
|
139
|
+
|
|
130
140
|
// ── Command registry ──────────────────────────────────────────────────────────
|
|
131
141
|
// Maps every command and alias to the module that implements it.
|
|
132
142
|
|
|
@@ -136,6 +146,7 @@ const COMMANDS = {
|
|
|
136
146
|
whoami: '../commands/whoami.mjs',
|
|
137
147
|
list: '../commands/list.mjs',
|
|
138
148
|
count: '../commands/count.mjs',
|
|
149
|
+
sum: '../commands/sum.mjs',
|
|
139
150
|
get: '../commands/get.mjs',
|
|
140
151
|
show: '../commands/get.mjs',
|
|
141
152
|
create: '../commands/create.mjs',
|
|
@@ -161,6 +172,7 @@ const COMMANDS = {
|
|
|
161
172
|
// exception: they accept arbitrary `--<field>` flags, marked with `null` below.
|
|
162
173
|
|
|
163
174
|
const ALWAYS_FLAGS = ['help', 'json', 'yaml', 'no-color', 'profile'];
|
|
175
|
+
const LEADING_FLAGS = [...ALWAYS_FLAGS, 'version', 'query'];
|
|
164
176
|
const SKILLS_FLAGS = ['target', 'dir', 'global', 'local', 'force', 'yes', 'no-logo'];
|
|
165
177
|
const OKF_FLAGS = ['dir', 'out', 'force', 'no-logo'];
|
|
166
178
|
const PROFILE_FLAGS = ['base-url', 'client-id', 'secret', 'local', 'from-current'];
|
|
@@ -173,6 +185,7 @@ const COMMAND_FLAGS = {
|
|
|
173
185
|
whoami: ['show-token'],
|
|
174
186
|
list: ['fields', 'filter', 'filter-file', 'sort', 'limit', 'offset', 'extdata', 'expand', 'query'],
|
|
175
187
|
count: ['filter', 'filter-file', 'query'],
|
|
188
|
+
sum: ['filter', 'filter-file', 'limit', 'offset', 'page-size', 'query'],
|
|
176
189
|
get: GET_FLAGS,
|
|
177
190
|
show: GET_FLAGS,
|
|
178
191
|
create: null,
|
|
@@ -192,6 +205,8 @@ const COMMAND_FLAGS = {
|
|
|
192
205
|
profiles: PROFILE_FLAGS,
|
|
193
206
|
};
|
|
194
207
|
|
|
208
|
+
const CREDENTIAL_MUTATION_COMMANDS = new Set(['login', 'logout', 'profile', 'profiles']);
|
|
209
|
+
|
|
195
210
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
196
211
|
|
|
197
212
|
async function main() {
|
|
@@ -208,20 +223,37 @@ async function main() {
|
|
|
208
223
|
process.exit(0);
|
|
209
224
|
}
|
|
210
225
|
|
|
211
|
-
const
|
|
226
|
+
const lead = _splitLeadingFlags(argv);
|
|
227
|
+
if (lead.values.help && lead.argv.length === 0) {
|
|
228
|
+
process.stdout.write(HELP);
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
if (lead.values.version && lead.argv.length === 0) {
|
|
232
|
+
process.stdout.write(_VERSION + '\n');
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
if (lead.argv.length === 0) {
|
|
236
|
+
process.stdout.write(HELP);
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
212
239
|
|
|
213
|
-
|
|
214
|
-
// bad option rather than letting it masquerade as one.
|
|
240
|
+
const command = lead.argv[0];
|
|
215
241
|
if (command.startsWith('-')) {
|
|
216
|
-
|
|
217
|
-
process.exit(1);
|
|
242
|
+
_failLeadingOption(command);
|
|
218
243
|
}
|
|
219
244
|
|
|
220
|
-
const rest = argv.slice(1);
|
|
245
|
+
const rest = lead.argv.slice(1);
|
|
246
|
+
|
|
247
|
+
if (process.env.ZEYOS_CREDENTIALS_READONLY && CREDENTIAL_MUTATION_COMMANDS.has(command)) {
|
|
248
|
+
process.stderr.write(`Credential command "${command}" is disabled because ZEYOS_CREDENTIALS_READONLY is set.\n`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
221
251
|
|
|
222
252
|
// Parse remaining args permissively: known options are parsed normally and
|
|
223
253
|
// unknown --key value flags are captured too (so create/update accept fields).
|
|
224
|
-
const
|
|
254
|
+
const parsed = _parsePermissive(rest, OPTIONS);
|
|
255
|
+
const values = { ...lead.values, ...parsed.values };
|
|
256
|
+
const positional = parsed.positional;
|
|
225
257
|
|
|
226
258
|
const modulePath = COMMANDS[command];
|
|
227
259
|
if (!modulePath) {
|
|
@@ -232,7 +264,7 @@ async function main() {
|
|
|
232
264
|
const mod = await import(modulePath);
|
|
233
265
|
|
|
234
266
|
if (values.help) {
|
|
235
|
-
process.stdout.write(mod.USAGE ?? HELP);
|
|
267
|
+
process.stdout.write(_formatCommandHelp(mod.USAGE ?? HELP));
|
|
236
268
|
process.exit(0);
|
|
237
269
|
}
|
|
238
270
|
|
|
@@ -254,11 +286,121 @@ async function main() {
|
|
|
254
286
|
}
|
|
255
287
|
}
|
|
256
288
|
|
|
289
|
+
_validateKnownStringValues(values);
|
|
290
|
+
|
|
257
291
|
await mod.run(values, positional);
|
|
258
292
|
}
|
|
259
293
|
|
|
260
294
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
261
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Parse documented global flags that appear before the command name, e.g.
|
|
298
|
+
* `zeyos --profile dev whoami`. Command-specific flags remain after the
|
|
299
|
+
* command so the per-command allow-list can validate them.
|
|
300
|
+
*/
|
|
301
|
+
function _splitLeadingFlags(argv) {
|
|
302
|
+
const values = {};
|
|
303
|
+
let i = 0;
|
|
304
|
+
|
|
305
|
+
while (i < argv.length) {
|
|
306
|
+
const arg = argv[i];
|
|
307
|
+
|
|
308
|
+
if (arg === '--') {
|
|
309
|
+
return { values, argv: argv.slice(i + 1) };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (arg.startsWith('--')) {
|
|
313
|
+
const eqIdx = arg.indexOf('=');
|
|
314
|
+
const key = eqIdx === -1 ? arg.slice(2) : arg.slice(2, eqIdx);
|
|
315
|
+
const inlineVal = eqIdx === -1 ? undefined : arg.slice(eqIdx + 1);
|
|
316
|
+
const opt = OPTIONS[key];
|
|
317
|
+
|
|
318
|
+
if (!LEADING_FLAGS.includes(key)) {
|
|
319
|
+
_failLeadingOption(arg);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (opt?.type === 'boolean') {
|
|
323
|
+
values[key] = true;
|
|
324
|
+
i++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (opt?.type === 'string') {
|
|
329
|
+
if (inlineVal !== undefined) {
|
|
330
|
+
values[key] = inlineVal;
|
|
331
|
+
i++;
|
|
332
|
+
} else {
|
|
333
|
+
const next = argv[i + 1];
|
|
334
|
+
if (next !== undefined && next.startsWith('--')) {
|
|
335
|
+
values[key] = '';
|
|
336
|
+
i++;
|
|
337
|
+
} else {
|
|
338
|
+
values[key] = next ?? '';
|
|
339
|
+
i += 2;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (arg.startsWith('-') && arg.length === 2) {
|
|
347
|
+
const short = arg[1];
|
|
348
|
+
const match = Object.entries(OPTIONS).find(([, o]) => o.short === short);
|
|
349
|
+
if (!match || !LEADING_FLAGS.includes(match[0])) {
|
|
350
|
+
_failLeadingOption(arg);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const [key, opt] = match;
|
|
354
|
+
if (opt.type === 'boolean') {
|
|
355
|
+
values[key] = true;
|
|
356
|
+
i++;
|
|
357
|
+
} else {
|
|
358
|
+
const next = argv[i + 1];
|
|
359
|
+
if (next !== undefined && next.startsWith('--')) {
|
|
360
|
+
values[key] = '';
|
|
361
|
+
i++;
|
|
362
|
+
} else {
|
|
363
|
+
values[key] = next ?? '';
|
|
364
|
+
i += 2;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { values, argv: argv.slice(i) };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function _failLeadingOption(flag) {
|
|
377
|
+
process.stderr.write(`Unknown option: "${flag}". Run 'zeyos --help' for usage.\n`);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function _formatCommandHelp(usage) {
|
|
382
|
+
if (usage === HELP || /--profile\s+<name>/.test(usage)) {
|
|
383
|
+
return usage;
|
|
384
|
+
}
|
|
385
|
+
if (/Global options:\n/.test(usage)) {
|
|
386
|
+
return usage.replace(
|
|
387
|
+
/Global options:\n/,
|
|
388
|
+
`Global options:\n --profile <name> Use a named credential profile for this command\n --no-color Disable ANSI colors\n`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const trimmed = usage.endsWith('\n') ? usage : `${usage}\n`;
|
|
392
|
+
return `${trimmed}\n${COMMON_COMMAND_HELP}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function _validateKnownStringValues(values) {
|
|
396
|
+
for (const [key, value] of Object.entries(values)) {
|
|
397
|
+
if (OPTIONS[key]?.type === 'string' && value === '') {
|
|
398
|
+
process.stderr.write(`Option --${key} requires a value.\n`);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
262
404
|
/**
|
|
263
405
|
* Parse argv with known options; capture unknown --key value pairs too.
|
|
264
406
|
* This lets create/update accept arbitrary --fieldName value flags.
|
package/commands/count.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { normalizeCountResult } from '@zeyos/client';
|
|
14
|
-
import { buildCliClient, callApi, maybeDryRun, parseJsonOptionOrFile, requireResource } from '../lib/command.mjs';
|
|
14
|
+
import { buildCliClient, callApi, maybeDryRun, normalizeFilterOperators, parseJsonOptionOrFile, requireResource } from '../lib/command.mjs';
|
|
15
15
|
import { outputMode, printJson, printYaml } from '../lib/output.mjs';
|
|
16
16
|
|
|
17
17
|
export const USAGE = `\
|
|
@@ -24,6 +24,8 @@ Arguments:
|
|
|
24
24
|
|
|
25
25
|
Options:
|
|
26
26
|
--filter <json> JSON filter object e.g. '{"status":1}'
|
|
27
|
+
Arrays normalize to IN; $lt/$lte/$gt/$gte/$ne/$in/$nin and suffix
|
|
28
|
+
keys like field__startswith/field__gt normalize to native operators
|
|
27
29
|
--filter-file <path>
|
|
28
30
|
Read JSON filter object from a file
|
|
29
31
|
--json Output as JSON ({ "count": N })
|
|
@@ -47,7 +49,7 @@ export async function run(values, positional) {
|
|
|
47
49
|
|
|
48
50
|
const filters = parseJsonOptionOrFile(values, 'filter', 'filter-file');
|
|
49
51
|
if (filters !== undefined) {
|
|
50
|
-
body.filters = filters;
|
|
52
|
+
body.filters = normalizeFilterOperators(filters, { fieldAliases: res.filterAliases });
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
// ── Call API ───────────────────────────────────────────────────────────────
|
package/commands/doctor.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const ENV_KEYS = {
|
|
|
21
21
|
ZEYOS_CLIENT_SECRET: 'clientSecret',
|
|
22
22
|
ZEYOS_TOKEN: 'accessToken',
|
|
23
23
|
ZEYOS_REFRESH_TOKEN: 'refreshToken',
|
|
24
|
+
ZEYOS_NO_REFRESH: 'tokenOnly',
|
|
25
|
+
ZEYOS_CREDENTIALS_READONLY: 'credentialsReadonly',
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
export const USAGE = `\
|
|
@@ -82,8 +84,9 @@ function buildAgentReport() {
|
|
|
82
84
|
clientSecret: Boolean(config.clientSecret),
|
|
83
85
|
accessToken: Boolean(config.accessToken),
|
|
84
86
|
refreshToken: Boolean(config.refreshToken),
|
|
87
|
+
tokenOnly: Boolean(process.env.ZEYOS_TOKEN) || isTruthyEnv(process.env.ZEYOS_NO_REFRESH),
|
|
85
88
|
};
|
|
86
|
-
const ready = Boolean(effective.baseUrl && effective.
|
|
89
|
+
const ready = Boolean(effective.baseUrl && effective.accessToken && (effective.tokenOnly || (effective.clientId && effective.clientSecret)));
|
|
87
90
|
const resources = inspectResources();
|
|
88
91
|
|
|
89
92
|
return {
|
|
@@ -117,6 +120,10 @@ function buildAgentReport() {
|
|
|
117
120
|
};
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
function isTruthyEnv(value) {
|
|
124
|
+
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
function inspectResources() {
|
|
121
128
|
const names = listResources();
|
|
122
129
|
const missing = [];
|
package/commands/list.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
callApi,
|
|
27
27
|
fail,
|
|
28
28
|
maybeDryRun,
|
|
29
|
+
normalizeFilterOperators,
|
|
29
30
|
parseJsonOptionOrFile,
|
|
30
31
|
requireApiMethod,
|
|
31
32
|
requireResource
|
|
@@ -42,6 +43,8 @@ Arguments:
|
|
|
42
43
|
Options:
|
|
43
44
|
--fields <list> Field selection (see formats below)
|
|
44
45
|
--filter <json> JSON filter object e.g. '{"status":1}'
|
|
46
|
+
Arrays normalize to IN; $lt/$lte/$gt/$gte/$ne/$in/$nin and suffix
|
|
47
|
+
keys like field__startswith/field__gt normalize to native operators
|
|
45
48
|
--filter-file <path>
|
|
46
49
|
Read JSON filter object from a file
|
|
47
50
|
--sort <fields> Sort expression e.g. '-lastmodified'
|
|
@@ -86,7 +89,7 @@ export async function run(values, positional) {
|
|
|
86
89
|
|
|
87
90
|
const filters = parseJsonOptionOrFile(values, 'filter', 'filter-file');
|
|
88
91
|
if (filters !== undefined) {
|
|
89
|
-
body.filters = filters;
|
|
92
|
+
body.filters = normalizeFilterOperators(filters, { fieldAliases: res.filterAliases });
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
if (values.sort) body.sort = values.sort.split(',').map(s => s.trim()).filter(Boolean);
|
package/commands/sum.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos sum <resource> <field>
|
|
3
|
+
*
|
|
4
|
+
* Page through records and sum one numeric field client-side.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { normalizeListResult } from '@zeyos/client';
|
|
8
|
+
import {
|
|
9
|
+
buildCliClient,
|
|
10
|
+
callApi,
|
|
11
|
+
fail,
|
|
12
|
+
maybeDryRun,
|
|
13
|
+
normalizeFilterOperators,
|
|
14
|
+
parseJsonOptionOrFile,
|
|
15
|
+
requireResource
|
|
16
|
+
} from '../lib/command.mjs';
|
|
17
|
+
import { outputMode, printJson, printYaml } from '../lib/output.mjs';
|
|
18
|
+
|
|
19
|
+
export const USAGE = `\
|
|
20
|
+
Usage: zeyos sum <resource> <field> [options]
|
|
21
|
+
|
|
22
|
+
Sum a numeric field across all records matching an optional filter.
|
|
23
|
+
The CLI pages internally so agents do not need to list rows and write ad hoc scripts.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
resource Resource name (e.g. actionsteps, transactions, payments)
|
|
27
|
+
field Numeric field to sum (e.g. effort, amount, netamount)
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--filter <json> JSON filter object e.g. '{"status":[1,3]}'
|
|
31
|
+
Arrays normalize to IN; $lt/$lte/$gt/$gte/$ne/$in/$nin and suffix
|
|
32
|
+
keys like field__startswith/field__gt normalize to native operators
|
|
33
|
+
--filter-file <path>
|
|
34
|
+
Read JSON filter object from a file
|
|
35
|
+
--page-size <n> Records per API page (default: 50)
|
|
36
|
+
--limit <n> Maximum records to inspect
|
|
37
|
+
--offset <n> Initial offset (default: 0)
|
|
38
|
+
--json Output as JSON ({ "sum": N, "count": N })
|
|
39
|
+
--yaml Output as YAML
|
|
40
|
+
--query Print the first page request without sending it
|
|
41
|
+
-h, --help Show this help
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
zeyos sum actionsteps effort --filter '{"status":[1,3]}'
|
|
45
|
+
zeyos sum transactions netamount --filter '{"type":3}' --json
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export async function run(values, positional) {
|
|
49
|
+
const resourceName = positional[0];
|
|
50
|
+
const field = positional[1];
|
|
51
|
+
const res = requireResource(resourceName, 'zeyos sum <resource> <field>');
|
|
52
|
+
if (!field) fail('Missing field name. Usage: zeyos sum <resource> <field>');
|
|
53
|
+
|
|
54
|
+
const pageSize = parsePositiveInt(values['page-size'] ?? '50', '--page-size');
|
|
55
|
+
const maxRows = values.limit == null ? Infinity : parsePositiveInt(values.limit, '--limit');
|
|
56
|
+
let offset = values.offset == null ? 0 : parseNonNegativeInt(values.offset, '--offset');
|
|
57
|
+
|
|
58
|
+
const body = { fields: [field], limit: Math.min(pageSize, maxRows), offset };
|
|
59
|
+
const filters = parseJsonOptionOrFile(values, 'filter', 'filter-file');
|
|
60
|
+
if (filters !== undefined) body.filters = normalizeFilterOperators(filters, { fieldAliases: res.filterAliases });
|
|
61
|
+
|
|
62
|
+
const clientState = buildCliClient(values);
|
|
63
|
+
if (await maybeDryRun(clientState, res.list, body, values)) return;
|
|
64
|
+
|
|
65
|
+
let sum = 0;
|
|
66
|
+
let count = 0;
|
|
67
|
+
|
|
68
|
+
while (count < maxRows) {
|
|
69
|
+
const remaining = maxRows - count;
|
|
70
|
+
const limit = Math.min(pageSize, remaining);
|
|
71
|
+
const pageBody = { ...body, limit, offset };
|
|
72
|
+
const result = await callApi(clientState, res.list, pageBody);
|
|
73
|
+
const rows = normalizeListResult(result).data;
|
|
74
|
+
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
sum += numericValue(row[field], field);
|
|
77
|
+
count += 1;
|
|
78
|
+
if (count >= maxRows) break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (rows.length < limit || rows.length === 0) break;
|
|
82
|
+
offset += rows.length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const mode = outputMode(values);
|
|
86
|
+
if (mode === 'json') {
|
|
87
|
+
printJson({ sum, count, field });
|
|
88
|
+
} else if (mode === 'yaml') {
|
|
89
|
+
printYaml({ sum, count, field });
|
|
90
|
+
} else {
|
|
91
|
+
process.stdout.write(`${sum}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function numericValue(value, field) {
|
|
96
|
+
if (value == null || value === '') return 0;
|
|
97
|
+
const n = Number(value);
|
|
98
|
+
if (!Number.isFinite(n)) fail(`Field "${field}" contains a non-numeric value: ${JSON.stringify(value)}`);
|
|
99
|
+
return n;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parsePositiveInt(value, flag) {
|
|
103
|
+
const n = Number.parseInt(String(value), 10);
|
|
104
|
+
if (!Number.isInteger(n) || n <= 0) fail(`${flag} must be a positive integer.`);
|
|
105
|
+
return n;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseNonNegativeInt(value, flag) {
|
|
109
|
+
const n = Number.parseInt(String(value), 10);
|
|
110
|
+
if (!Number.isInteger(n) || n < 0) fail(`${flag} must be a non-negative integer.`);
|
|
111
|
+
return n;
|
|
112
|
+
}
|
package/lib/client.mjs
CHANGED
|
@@ -23,29 +23,47 @@ export function buildClient(overrides = {}, opts = {}) {
|
|
|
23
23
|
throw new Error(`Profile "${loaded.profile.name}" not found (selected via ${loaded.profile.origin}). ${known}`);
|
|
24
24
|
}
|
|
25
25
|
const config = { ...loaded.config, ...overrides };
|
|
26
|
-
|
|
26
|
+
const tokenOnly = isTokenOnlyMode(config);
|
|
27
|
+
requireConfig(tokenOnly ? ['baseUrl', 'accessToken'] : ['baseUrl', 'clientId', 'clientSecret', 'accessToken'], config);
|
|
27
28
|
|
|
28
|
-
const tokenStore = new MemoryTokenStore(
|
|
29
|
-
accessToken:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const tokenStore = new MemoryTokenStore(tokenOnly
|
|
30
|
+
? { accessToken: config.accessToken }
|
|
31
|
+
: {
|
|
32
|
+
accessToken: config.accessToken,
|
|
33
|
+
refreshToken: config.refreshToken,
|
|
34
|
+
expiresAt: config.expiresAt,
|
|
35
|
+
refreshTokenExpiresAt: config.refreshTokenExpiresAt,
|
|
36
|
+
});
|
|
34
37
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const oauth = tokenOnly
|
|
39
|
+
? {
|
|
40
|
+
tokenStore,
|
|
41
|
+
autoRefresh: false,
|
|
42
|
+
}
|
|
43
|
+
: {
|
|
40
44
|
clientId: config.clientId,
|
|
41
45
|
clientSecret: config.clientSecret,
|
|
42
46
|
tokenStore,
|
|
43
47
|
autoRefresh: true,
|
|
44
|
-
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const client = createZeyosClient({
|
|
51
|
+
platform: config.baseUrl,
|
|
52
|
+
auth: {
|
|
53
|
+
mode: 'oauth',
|
|
54
|
+
oauth,
|
|
45
55
|
},
|
|
46
56
|
});
|
|
47
57
|
|
|
48
|
-
return { client, config, tokenStore, configSource: loaded.source };
|
|
58
|
+
return { client, config, tokenStore, configSource: tokenOnly ? null : loaded.source, tokenOnly };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isTruthyEnv(value) {
|
|
62
|
+
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isTokenOnlyMode(config) {
|
|
66
|
+
return Boolean(process.env.ZEYOS_TOKEN) || (isTruthyEnv(process.env.ZEYOS_NO_REFRESH) && Boolean(config.accessToken));
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
/**
|
package/lib/command.mjs
CHANGED
|
@@ -99,6 +99,134 @@ export function parseJsonOptionOrFile(values, flagName, fileFlagName = `${flagNa
|
|
|
99
99
|
return undefined;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
const FILTER_OPERATOR_ALIASES = {
|
|
103
|
+
$lt: '<',
|
|
104
|
+
$lte: '<=',
|
|
105
|
+
$gt: '>',
|
|
106
|
+
$gte: '>=',
|
|
107
|
+
$ne: '!=',
|
|
108
|
+
$in: 'IN',
|
|
109
|
+
$nin: '!IN',
|
|
110
|
+
$notIn: '!IN',
|
|
111
|
+
lt: '<',
|
|
112
|
+
lte: '<=',
|
|
113
|
+
gt: '>',
|
|
114
|
+
gte: '>=',
|
|
115
|
+
ne: '!=',
|
|
116
|
+
in: 'IN',
|
|
117
|
+
nin: '!IN',
|
|
118
|
+
notIn: '!IN',
|
|
119
|
+
notin: '!IN'
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const FILTER_SUFFIX_OPERATOR_ALIASES = {
|
|
123
|
+
lt: '<',
|
|
124
|
+
lte: '<=',
|
|
125
|
+
gt: '>',
|
|
126
|
+
gte: '>=',
|
|
127
|
+
ne: '!=',
|
|
128
|
+
in: 'IN',
|
|
129
|
+
nin: '!IN',
|
|
130
|
+
notin: '!IN'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const FILTER_PATTERN_SUFFIXES = new Set([
|
|
134
|
+
'startswith',
|
|
135
|
+
'istartswith',
|
|
136
|
+
'like',
|
|
137
|
+
'ilike',
|
|
138
|
+
'contains',
|
|
139
|
+
'icontains',
|
|
140
|
+
'regex',
|
|
141
|
+
'iregex'
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
export function normalizeFilterOperators(value, options = {}) {
|
|
145
|
+
if (Array.isArray(value)) {
|
|
146
|
+
return value.map((item) => normalizeFilterOperators(item, options));
|
|
147
|
+
}
|
|
148
|
+
if (!value || typeof value !== 'object') return value;
|
|
149
|
+
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const [key, child] of Object.entries(value)) {
|
|
152
|
+
const suffixFilter = parseFilterSuffix(key, child, options);
|
|
153
|
+
if (suffixFilter) {
|
|
154
|
+
mergeFieldFilter(out, suffixFilter.field, suffixFilter.operator, suffixFilter.value);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const normalizedKey = FILTER_OPERATOR_ALIASES[key] || key;
|
|
159
|
+
const outputKey = isFilterOperatorKey(normalizedKey)
|
|
160
|
+
? normalizedKey
|
|
161
|
+
: normalizeFieldAlias(normalizedKey, options);
|
|
162
|
+
const normalizedChild = normalizeFilterOperators(child, options);
|
|
163
|
+
out[outputKey] = Array.isArray(normalizedChild) && !isFilterOperatorKey(outputKey)
|
|
164
|
+
? { IN: normalizedChild }
|
|
165
|
+
: normalizedChild;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isFilterOperatorKey(key) {
|
|
171
|
+
return ['<', '<=', '>', '>=', '!=', 'IN', '!IN', '~~*'].includes(key);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeFieldAlias(field, options = {}) {
|
|
175
|
+
return options.fieldAliases?.[field] || field;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseFilterSuffix(key, child, options) {
|
|
179
|
+
const separator = key.lastIndexOf('__');
|
|
180
|
+
if (separator <= 0) return null;
|
|
181
|
+
|
|
182
|
+
const rawField = key.slice(0, separator);
|
|
183
|
+
const suffix = key.slice(separator + 2).toLowerCase();
|
|
184
|
+
const field = normalizeFieldAlias(rawField, options);
|
|
185
|
+
|
|
186
|
+
if (Object.prototype.hasOwnProperty.call(FILTER_SUFFIX_OPERATOR_ALIASES, suffix)) {
|
|
187
|
+
return {
|
|
188
|
+
field,
|
|
189
|
+
operator: FILTER_SUFFIX_OPERATOR_ALIASES[suffix],
|
|
190
|
+
value: normalizeFilterOperators(child, options)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (FILTER_PATTERN_SUFFIXES.has(suffix)) {
|
|
195
|
+
return {
|
|
196
|
+
field,
|
|
197
|
+
operator: '~~*',
|
|
198
|
+
value: patternValueForSuffix(suffix, child)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function patternValueForSuffix(suffix, child) {
|
|
206
|
+
const value = String(child ?? '');
|
|
207
|
+
if (suffix === 'startswith' || suffix === 'istartswith') return `${value}%`;
|
|
208
|
+
if (suffix === 'contains' || suffix === 'icontains') return `%${value}%`;
|
|
209
|
+
if (suffix === 'regex' || suffix === 'iregex') return regexLikeToSqlLike(value);
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function regexLikeToSqlLike(value) {
|
|
214
|
+
return String(value)
|
|
215
|
+
.replace(/^\^/, '')
|
|
216
|
+
.replace(/\$$/, '')
|
|
217
|
+
.replace(/\\.\\*/g, '%')
|
|
218
|
+
.replace(/\.\*/g, '%');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function mergeFieldFilter(out, field, operator, value) {
|
|
222
|
+
const existing = out[field];
|
|
223
|
+
if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
|
|
224
|
+
out[field] = { ...existing, [operator]: value };
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
out[field] = { [operator]: value };
|
|
228
|
+
}
|
|
229
|
+
|
|
102
230
|
/** Cheap structural check: does this string look like an intended JSON object? */
|
|
103
231
|
function looksLikeJsonObject(value) {
|
|
104
232
|
return typeof value === 'string' && value.trim().startsWith('{');
|
package/lib/config.mjs
CHANGED
|
@@ -60,6 +60,9 @@ export function loadConfig(opts = {}) {
|
|
|
60
60
|
*/
|
|
61
61
|
export function loadConfigWithSource(opts = {}) {
|
|
62
62
|
const env = _fromEnv();
|
|
63
|
+
if (env.accessToken) {
|
|
64
|
+
return { config: env, source: null, profile: null };
|
|
65
|
+
}
|
|
63
66
|
const selection = resolveProfileSelection({ profileFlag: opts.profile });
|
|
64
67
|
|
|
65
68
|
let base = {};
|
package/lib/resource-config.mjs
CHANGED
|
@@ -73,7 +73,7 @@ export function loadResourceConfig(name) {
|
|
|
73
73
|
export function getListFields(res, name, override) {
|
|
74
74
|
// 1. CLI override
|
|
75
75
|
if (override) {
|
|
76
|
-
return _parseFieldsOverride(override);
|
|
76
|
+
return _parseFieldsOverride(override, res?.fieldAliases);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// 2. Config file
|
|
@@ -181,7 +181,7 @@ export function getGetParams(name) {
|
|
|
181
181
|
* Parse a --fields override string.
|
|
182
182
|
* Supports: comma-separated, JSON object, JSON array.
|
|
183
183
|
*/
|
|
184
|
-
function _parseFieldsOverride(raw) {
|
|
184
|
+
function _parseFieldsOverride(raw, fieldAliases = {}) {
|
|
185
185
|
const trimmed = raw.trim();
|
|
186
186
|
|
|
187
187
|
// JSON object: {"Alias": "path", ...}
|
|
@@ -189,7 +189,7 @@ function _parseFieldsOverride(raw) {
|
|
|
189
189
|
try {
|
|
190
190
|
const obj = JSON.parse(trimmed);
|
|
191
191
|
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
|
192
|
-
const apiFields = _toFieldAliasMap(obj);
|
|
192
|
+
const apiFields = _toFieldAliasMap(obj, fieldAliases);
|
|
193
193
|
return { apiFields, displayColumns: Object.keys(apiFields) };
|
|
194
194
|
}
|
|
195
195
|
} catch (e) {
|
|
@@ -205,7 +205,7 @@ function _parseFieldsOverride(raw) {
|
|
|
205
205
|
if (Array.isArray(arr)) {
|
|
206
206
|
const paths = arr.map(String);
|
|
207
207
|
const apiFields = {};
|
|
208
|
-
for (const p of paths) apiFields[p] = p;
|
|
208
|
+
for (const p of paths) apiFields[p] = normalizeFieldAlias(p, fieldAliases);
|
|
209
209
|
return { apiFields, displayColumns: paths };
|
|
210
210
|
}
|
|
211
211
|
} catch (e) {
|
|
@@ -217,7 +217,7 @@ function _parseFieldsOverride(raw) {
|
|
|
217
217
|
// Comma-separated: "ID,name,status"
|
|
218
218
|
const paths = trimmed.split(',').map(s => s.trim()).filter(Boolean);
|
|
219
219
|
const apiFields = {};
|
|
220
|
-
for (const p of paths) apiFields[p] = p;
|
|
220
|
+
for (const p of paths) apiFields[p] = normalizeFieldAlias(p, fieldAliases);
|
|
221
221
|
return { apiFields, displayColumns: paths };
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -227,14 +227,18 @@ function _parseFieldsOverride(raw) {
|
|
|
227
227
|
* @param {Record<string, JsonValue>} value
|
|
228
228
|
* @returns {Record<string,string>}
|
|
229
229
|
*/
|
|
230
|
-
function _toFieldAliasMap(value) {
|
|
230
|
+
function _toFieldAliasMap(value, fieldAliases = {}) {
|
|
231
231
|
const fields = {};
|
|
232
232
|
for (const [alias, field] of Object.entries(value)) {
|
|
233
|
-
fields[String(alias)] = String(field);
|
|
233
|
+
fields[String(alias)] = normalizeFieldAlias(String(field), fieldAliases);
|
|
234
234
|
}
|
|
235
235
|
return fields;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
function normalizeFieldAlias(field, fieldAliases = {}) {
|
|
239
|
+
return fieldAliases[field] || field;
|
|
240
|
+
}
|
|
241
|
+
|
|
238
242
|
// ── Internals ────────────────────────────────────────────────────────────────
|
|
239
243
|
|
|
240
244
|
/**
|
package/lib/resources.mjs
CHANGED
|
@@ -45,6 +45,8 @@ const REGISTRY = {
|
|
|
45
45
|
update: 'updateAccount',
|
|
46
46
|
delete: 'deleteAccount',
|
|
47
47
|
fields: ['ID', 'customernum', 'lastname', 'firstname', 'type', 'assigneduser', 'lastmodified'],
|
|
48
|
+
fieldAliases: { name: 'lastname' },
|
|
49
|
+
filterAliases: { name: 'lastname' },
|
|
48
50
|
},
|
|
49
51
|
contact: {
|
|
50
52
|
list: 'listContacts',
|
|
@@ -54,6 +56,14 @@ const REGISTRY = {
|
|
|
54
56
|
delete: 'deleteContact',
|
|
55
57
|
fields: ['ID', 'firstname', 'lastname', 'email', 'phone', 'account'],
|
|
56
58
|
},
|
|
59
|
+
address: {
|
|
60
|
+
list: 'listAddresses',
|
|
61
|
+
get: 'getAddress',
|
|
62
|
+
create: 'createAddress',
|
|
63
|
+
update: 'updateAddress',
|
|
64
|
+
delete: 'deleteAddress',
|
|
65
|
+
fields: ['ID', 'account', 'contact', 'type', 'default'],
|
|
66
|
+
},
|
|
57
67
|
project: {
|
|
58
68
|
list: 'listProjects',
|
|
59
69
|
get: 'getProject',
|
|
@@ -112,6 +122,11 @@ const REGISTRY = {
|
|
|
112
122
|
get: 'getGroup',
|
|
113
123
|
fields: ['ID', 'name', 'description'],
|
|
114
124
|
},
|
|
125
|
+
groupuser: {
|
|
126
|
+
list: 'listGroupsToUsers',
|
|
127
|
+
get: 'getGroupToUser',
|
|
128
|
+
fields: ['ID', 'group', 'user'],
|
|
129
|
+
},
|
|
115
130
|
event: {
|
|
116
131
|
list: 'listEvents',
|
|
117
132
|
get: 'getEvent',
|
|
@@ -152,6 +167,62 @@ const REGISTRY = {
|
|
|
152
167
|
delete: 'deleteCampaign',
|
|
153
168
|
fields: ['ID', 'name', 'status', 'startdate', 'enddate'],
|
|
154
169
|
},
|
|
170
|
+
mailinglist: {
|
|
171
|
+
list: 'listMailingLists',
|
|
172
|
+
get: 'getMailingList',
|
|
173
|
+
create: 'createMailingList',
|
|
174
|
+
update: 'updateMailingList',
|
|
175
|
+
delete: 'deleteMailingList',
|
|
176
|
+
fields: ['ID', 'name', 'description', 'status', 'lastmodified'],
|
|
177
|
+
},
|
|
178
|
+
mailingrecipient: {
|
|
179
|
+
list: 'listMailingRecipients',
|
|
180
|
+
get: 'getMailingRecipient',
|
|
181
|
+
create: 'createMailingRecipient',
|
|
182
|
+
update: 'updateMailingRecipient',
|
|
183
|
+
delete: 'deleteMailingRecipient',
|
|
184
|
+
fields: ['ID', 'message', 'mailinglist', 'campaign', 'email', 'recipientuser', 'recipientgroup'],
|
|
185
|
+
},
|
|
186
|
+
dunning: {
|
|
187
|
+
list: 'listDunningNotices',
|
|
188
|
+
get: 'getDunningNotice',
|
|
189
|
+
create: 'createDunningNotice',
|
|
190
|
+
update: 'updateDunningNotice',
|
|
191
|
+
delete: 'deleteDunningNotice',
|
|
192
|
+
fields: ['ID', 'dunningnum', 'type', 'status', 'date', 'duedate', 'account', 'recipient', 'fee'],
|
|
193
|
+
},
|
|
194
|
+
dunningtransaction: {
|
|
195
|
+
list: 'listDunningToTransactions',
|
|
196
|
+
get: 'getDunningToTransaction',
|
|
197
|
+
create: 'createDunningToTransaction',
|
|
198
|
+
update: 'updateDunningToTransaction',
|
|
199
|
+
delete: 'deleteDunningToTransaction',
|
|
200
|
+
fields: ['ID', 'dunning', 'transaction'],
|
|
201
|
+
},
|
|
202
|
+
pricelist: {
|
|
203
|
+
list: 'listPriceLists',
|
|
204
|
+
get: 'getPriceList',
|
|
205
|
+
create: 'createPriceList',
|
|
206
|
+
update: 'updatePriceList',
|
|
207
|
+
delete: 'deletePriceList',
|
|
208
|
+
fields: ['ID', 'name', 'type', 'discount', 'allaccounts'],
|
|
209
|
+
},
|
|
210
|
+
pricelistaccount: {
|
|
211
|
+
list: 'listPriceListsToAccounts',
|
|
212
|
+
get: 'getPriceListToAccount',
|
|
213
|
+
create: 'createPriceListToAccount',
|
|
214
|
+
update: 'updatePriceListToAccount',
|
|
215
|
+
delete: 'deletePriceListToAccount',
|
|
216
|
+
fields: ['ID', 'pricelist', 'account'],
|
|
217
|
+
},
|
|
218
|
+
price: {
|
|
219
|
+
list: 'listPrices',
|
|
220
|
+
get: 'getPrice',
|
|
221
|
+
create: 'createPrice',
|
|
222
|
+
update: 'updatePrice',
|
|
223
|
+
delete: 'deletePrice',
|
|
224
|
+
fields: ['ID', 'pricelist', 'item', 'price', 'rebate', 'discount'],
|
|
225
|
+
},
|
|
155
226
|
customfield: {
|
|
156
227
|
list: 'listCustomFields',
|
|
157
228
|
get: 'getCustomField',
|
|
@@ -198,6 +269,7 @@ const ALIASES = {
|
|
|
198
269
|
tasks: 'task',
|
|
199
270
|
accounts: 'account',
|
|
200
271
|
contacts: 'contact',
|
|
272
|
+
addresses: 'address',
|
|
201
273
|
projects: 'project',
|
|
202
274
|
appointments: 'appointment',
|
|
203
275
|
documents: 'document',
|
|
@@ -208,11 +280,46 @@ const ALIASES = {
|
|
|
208
280
|
items: 'item',
|
|
209
281
|
users: 'user',
|
|
210
282
|
groups: 'group',
|
|
283
|
+
groupuser: 'groupuser',
|
|
284
|
+
groupusers: 'groupuser',
|
|
285
|
+
groups2user: 'groupuser',
|
|
286
|
+
groups2users: 'groupuser',
|
|
287
|
+
'group-user': 'groupuser',
|
|
288
|
+
'group-users': 'groupuser',
|
|
289
|
+
'groups-to-user': 'groupuser',
|
|
290
|
+
'groups-to-users': 'groupuser',
|
|
211
291
|
events: 'event',
|
|
212
292
|
transactions: 'transaction',
|
|
213
293
|
payments: 'payment',
|
|
214
294
|
opportunities:'opportunity',
|
|
215
295
|
campaigns: 'campaign',
|
|
296
|
+
mailinglists: 'mailinglist',
|
|
297
|
+
'mailing-list': 'mailinglist',
|
|
298
|
+
'mailing-lists': 'mailinglist',
|
|
299
|
+
mailingrecipients: 'mailingrecipient',
|
|
300
|
+
'mailing-recipient': 'mailingrecipient',
|
|
301
|
+
'mailing-recipients': 'mailingrecipient',
|
|
302
|
+
dunnings: 'dunning',
|
|
303
|
+
'dunning-notice': 'dunning',
|
|
304
|
+
'dunning-notices': 'dunning',
|
|
305
|
+
dunningnotice: 'dunning',
|
|
306
|
+
dunningnotices: 'dunning',
|
|
307
|
+
dunning2transaction: 'dunningtransaction',
|
|
308
|
+
dunning2transactions: 'dunningtransaction',
|
|
309
|
+
'dunning-transaction': 'dunningtransaction',
|
|
310
|
+
'dunning-transactions': 'dunningtransaction',
|
|
311
|
+
'dunning-to-transaction': 'dunningtransaction',
|
|
312
|
+
'dunning-to-transactions': 'dunningtransaction',
|
|
313
|
+
pricelists: 'pricelist',
|
|
314
|
+
'price-list': 'pricelist',
|
|
315
|
+
'price-lists': 'pricelist',
|
|
316
|
+
pricelistaccount: 'pricelistaccount',
|
|
317
|
+
pricelistaccounts: 'pricelistaccount',
|
|
318
|
+
pricelists2account: 'pricelistaccount',
|
|
319
|
+
pricelists2accounts: 'pricelistaccount',
|
|
320
|
+
'price-list-account': 'pricelistaccount',
|
|
321
|
+
'price-list-accounts': 'pricelistaccount',
|
|
322
|
+
prices: 'price',
|
|
216
323
|
customfields: 'customfield',
|
|
217
324
|
custom_fields: 'customfield',
|
|
218
325
|
'custom-fields': 'customfield',
|
package/lib/types.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeyos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Command-line interface for the ZeyOS API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=18.3"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@zeyos/client": "^0.
|
|
42
|
+
"@zeyos/client": "^0.6.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"test": "node --test test/offline.mjs"
|