@aderis-erp/cli 1.0.3 → 1.0.7

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.
Files changed (111) hide show
  1. package/README.md +6 -0
  2. package/dist/commands/access-groups.d.ts +1 -0
  3. package/dist/commands/access-groups.js +84 -28
  4. package/dist/commands/{async-endpoints-internal.d.ts → account-transaction-allocations.d.ts} +3 -2
  5. package/dist/commands/{async-endpoints-internal.js → account-transaction-allocations.js} +93 -33
  6. package/dist/commands/account-transactions.d.ts +1 -0
  7. package/dist/commands/account-transactions.js +96 -40
  8. package/dist/commands/accounts.d.ts +1 -0
  9. package/dist/commands/accounts.js +89 -33
  10. package/dist/commands/agent-runs.d.ts +1 -0
  11. package/dist/commands/agent-runs.js +85 -27
  12. package/dist/commands/audit-log.d.ts +1 -0
  13. package/dist/commands/audit-log.js +82 -26
  14. package/dist/commands/auth/login.js +1 -2
  15. package/dist/commands/auth/tenant.d.ts +1 -1
  16. package/dist/commands/auth/tenant.js +11 -11
  17. package/dist/commands/auth.js +1 -1
  18. package/dist/commands/bank-accounts.d.ts +1 -0
  19. package/dist/commands/bank-accounts.js +96 -40
  20. package/dist/commands/bank-integrations.d.ts +1 -0
  21. package/dist/commands/bank-integrations.js +97 -41
  22. package/dist/commands/business-partners---contacts.d.ts +1 -0
  23. package/dist/commands/business-partners---contacts.js +89 -33
  24. package/dist/commands/business-partners.d.ts +1 -0
  25. package/dist/commands/business-partners.js +89 -33
  26. package/dist/commands/credit-card-accounts.d.ts +1 -0
  27. package/dist/commands/credit-card-accounts.js +94 -38
  28. package/dist/commands/documents.d.ts +1 -0
  29. package/dist/commands/documents.js +85 -29
  30. package/dist/commands/dunning-configs.d.ts +1 -0
  31. package/dist/commands/dunning-configs.js +84 -28
  32. package/dist/commands/dunning-reminders.d.ts +1 -0
  33. package/dist/commands/dunning-reminders.js +87 -31
  34. package/dist/commands/dunning-runs.d.ts +1 -0
  35. package/dist/commands/dunning-runs.js +80 -24
  36. package/dist/commands/employees.d.ts +1 -0
  37. package/dist/commands/employees.js +111 -55
  38. package/dist/commands/event-processor-mappings.d.ts +1 -0
  39. package/dist/commands/event-processor-mappings.js +84 -28
  40. package/dist/commands/files-internal.d.ts +1 -0
  41. package/dist/commands/files-internal.js +88 -32
  42. package/dist/commands/health.d.ts +1 -0
  43. package/dist/commands/health.js +84 -28
  44. package/dist/commands/inbox.d.ts +1 -0
  45. package/dist/commands/inbox.js +84 -28
  46. package/dist/commands/integrations.d.ts +1 -0
  47. package/dist/commands/integrations.js +82 -26
  48. package/dist/commands/item-categories.d.ts +1 -0
  49. package/dist/commands/item-categories.js +84 -28
  50. package/dist/commands/items.d.ts +1 -0
  51. package/dist/commands/items.js +84 -28
  52. package/dist/commands/journal-lines.d.ts +1 -0
  53. package/dist/commands/journal-lines.js +80 -24
  54. package/dist/commands/journals.d.ts +1 -0
  55. package/dist/commands/journals.js +98 -42
  56. package/dist/commands/ledgers.d.ts +1 -0
  57. package/dist/commands/ledgers.js +95 -39
  58. package/dist/commands/office-locations.d.ts +1 -0
  59. package/dist/commands/office-locations.js +84 -28
  60. package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
  61. package/dist/commands/payment-method-ledger-mappings.js +84 -28
  62. package/dist/commands/payment-methods.d.ts +1 -0
  63. package/dist/commands/payment-methods.js +84 -28
  64. package/dist/commands/payment-runs.d.ts +1 -0
  65. package/dist/commands/payment-runs.js +97 -41
  66. package/dist/commands/payment-terms.d.ts +1 -0
  67. package/dist/commands/payment-terms.js +85 -29
  68. package/dist/commands/price-lists.d.ts +1 -0
  69. package/dist/commands/price-lists.js +89 -33
  70. package/dist/commands/processor-mappings.d.ts +1 -0
  71. package/dist/commands/processor-mappings.js +83 -27
  72. package/dist/commands/processors.d.ts +1 -0
  73. package/dist/commands/processors.js +88 -32
  74. package/dist/commands/reconciliation.d.ts +1 -0
  75. package/dist/commands/reconciliation.js +85 -29
  76. package/dist/commands/reports.d.ts +1 -0
  77. package/dist/commands/reports.js +85 -26
  78. package/dist/commands/salary-types.d.ts +1 -0
  79. package/dist/commands/salary-types.js +84 -28
  80. package/dist/commands/sales-invoices.d.ts +1 -0
  81. package/dist/commands/sales-invoices.js +109 -53
  82. package/dist/commands/sales-orders.d.ts +1 -0
  83. package/dist/commands/sales-orders.js +107 -51
  84. package/dist/commands/tags.d.ts +1 -0
  85. package/dist/commands/tags.js +84 -28
  86. package/dist/commands/tax-codes.d.ts +1 -0
  87. package/dist/commands/tax-codes.js +87 -31
  88. package/dist/commands/tax-matrix.d.ts +1 -0
  89. package/dist/commands/tax-matrix.js +84 -28
  90. package/dist/commands/tenants.d.ts +1 -0
  91. package/dist/commands/tenants.js +84 -28
  92. package/dist/commands/text-templates.d.ts +1 -0
  93. package/dist/commands/text-templates.js +84 -28
  94. package/dist/commands/tools.d.ts +1 -0
  95. package/dist/commands/tools.js +81 -25
  96. package/dist/commands/vat-declaration.d.ts +41 -0
  97. package/dist/commands/vat-declaration.js +1079 -0
  98. package/dist/commands/vendor-invoices.d.ts +1 -0
  99. package/dist/commands/vendor-invoices.js +113 -57
  100. package/dist/commands/webhooks.d.ts +1 -0
  101. package/dist/commands/webhooks.js +86 -30
  102. package/dist/commands/workflows.d.ts +1 -0
  103. package/dist/commands/workflows.js +82 -26
  104. package/dist/lib/api-client.d.ts +6 -1
  105. package/dist/lib/api-client.js +23 -5
  106. package/dist/lib/auth.js +18 -0
  107. package/dist/lib/config.js +1 -1
  108. package/oclif.manifest.json +229 -52
  109. package/package.json +15 -13
  110. package/dist/index 2.js +0 -1
  111. package/dist/index.d 2.ts +0 -1
@@ -6,11 +6,13 @@ const HAS_IMPORT = false;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["list", "get", "retry"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "get", "retry", "restart", "discard"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/agent-runs', summary: 'List agent runs for the current tenant', pathParams: [], hasBody: false, isMultipart: false },
12
- 'get': { method: 'GET', path: '/v1/agent-runs/{runId}', summary: 'Get an agent-run detail by id', pathParams: ['runId'], hasBody: false, isMultipart: false },
13
- 'retry': { method: 'POST', path: '/v1/agent-runs/{runId}/retry', summary: 'Retry an agent run from NEEDS_REVIEW with a reviewer instruction', pathParams: ['runId'], hasBody: true, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/agent-runs', summary: 'List agent runs for the current tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'get': { method: 'GET', path: '/v1/agent-runs/{runId}', summary: 'Get an agent-run detail by id', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
+ 'retry': { method: 'POST', path: '/v1/agent-runs/{runId}/retry', summary: 'Retry an agent run from NEEDS_REVIEW with a reviewer instruction', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'restart': { method: 'POST', path: '/v1/agent-runs/{runId}/restart', summary: 'Restart a FAILED agent run from its existing state', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
+ 'discard': { method: 'POST', path: '/v1/agent-runs/{runId}/discard', summary: 'Discard an agent run with an optional reviewer reason', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
16
  };
15
17
  export default class AgentRuns extends Command {
16
18
  static description = 'Manage agent runs';
@@ -76,11 +78,6 @@ export default class AgentRuns extends Command {
76
78
  const endpoint = ENDPOINTS[args.action];
77
79
  if (!endpoint)
78
80
  this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
79
- const context = flags.context ? getContext(flags.context) : getCurrentContext();
80
- if (!context)
81
- this.error('No context configured. Run: aderis auth login --new');
82
- if (!context.tokens)
83
- this.error('Not logged in. Run: aderis auth login');
84
81
  let apiPath = endpoint.path;
85
82
  for (let i = 0; i < endpoint.pathParams.length; i++) {
86
83
  const param = endpoint.pathParams[i];
@@ -99,6 +96,11 @@ export default class AgentRuns extends Command {
99
96
  }
100
97
  apiPath = apiPath.replace(`{${param}}`, String(value));
101
98
  }
99
+ const context = flags.context ? getContext(flags.context) : getCurrentContext();
100
+ if (!context)
101
+ this.error('No context configured. Run: aderis auth login --new');
102
+ if (!context.tokens)
103
+ this.error('Not logged in. Run: aderis auth login');
102
104
  const queryParams = {};
103
105
  if (flags.page)
104
106
  queryParams.page = flags.page;
@@ -125,15 +127,7 @@ export default class AgentRuns extends Command {
125
127
  if (eqIndex === -1)
126
128
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
127
129
  const key = f.slice(0, eqIndex);
128
- let value = f.slice(eqIndex + 1);
129
- // Check for operator prefix (with or without $)
130
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
131
- if (opMatch) {
132
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
133
- }
134
- else {
135
- value = `\$eq:${value}`;
136
- }
130
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
137
131
  const filterKey = `filter.${key}`;
138
132
  if (filterKey in queryParams) {
139
133
  // Multiple filters on same field: collect as array
@@ -166,7 +160,9 @@ export default class AgentRuns extends Command {
166
160
  data = form;
167
161
  }
168
162
  else if (endpoint.hasBody) {
169
- if (flags.file) {
163
+ // For binary-response endpoints, --file is the output path, not a JSON
164
+ // input file — skip the read-as-body branch.
165
+ if (flags.file && !endpoint.isBinaryResponse) {
170
166
  const fsModule = await import('node:fs');
171
167
  try {
172
168
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -188,6 +184,7 @@ export default class AgentRuns extends Command {
188
184
  if (!data)
189
185
  data = {};
190
186
  const body = data;
187
+ const arrayFields = new Set(endpoint.bodyArrayFields);
191
188
  for (const pair of flags.set) {
192
189
  const eqIndex = pair.indexOf('=');
193
190
  if (eqIndex === -1)
@@ -199,6 +196,19 @@ export default class AgentRuns extends Command {
199
196
  value = JSON.parse(value);
200
197
  }
201
198
  catch { /* keep as string */ }
199
+ // For top-level keys typed as array in the API schema, split bare
200
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
201
+ // wrap a lone scalar in a single-element array. Skips when --set
202
+ // already provided a JSON array.
203
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
204
+ if (isArrayField && !Array.isArray(value)) {
205
+ if (typeof value === 'string') {
206
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
207
+ }
208
+ else if (value !== undefined && value !== null) {
209
+ value = [value];
210
+ }
211
+ }
202
212
  // Support nested keys like "address.city"
203
213
  const keys = key.split('.');
204
214
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -249,6 +259,26 @@ export default class AgentRuns extends Command {
249
259
  process.stderr.write('\n');
250
260
  }
251
261
  try {
262
+ // Binary response: download to file
263
+ if (endpoint.isBinaryResponse) {
264
+ if (!flags.file) {
265
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
266
+ }
267
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
268
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
269
+ data,
270
+ contextName: flags.context,
271
+ binary: true,
272
+ });
273
+ const fsModule = await import('node:fs');
274
+ const binData = binResponse.data;
275
+ const buffer = Buffer.isBuffer(binData)
276
+ ? binData
277
+ : Buffer.from(binData);
278
+ fsModule.writeFileSync(flags.file, buffer);
279
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
280
+ return;
281
+ }
252
282
  const response = await apiRequest(endpoint.method, apiPath, {
253
283
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
254
284
  data,
@@ -339,6 +369,7 @@ export default class AgentRuns extends Command {
339
369
  }
340
370
  else if (flags.set && flags.set.length > 0) {
341
371
  const item = {};
372
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
342
373
  for (const pair of flags.set) {
343
374
  const eqIndex = pair.indexOf('=');
344
375
  if (eqIndex === -1)
@@ -349,6 +380,15 @@ export default class AgentRuns extends Command {
349
380
  value = JSON.parse(value);
350
381
  }
351
382
  catch { /* keep as string */ }
383
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
384
+ if (isArrayField && !Array.isArray(value)) {
385
+ if (typeof value === 'string') {
386
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
387
+ }
388
+ else if (value !== undefined && value !== null) {
389
+ value = [value];
390
+ }
391
+ }
352
392
  const keys = key.split('.');
353
393
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
354
394
  this.error(`Invalid --set key: ${key}`);
@@ -498,14 +538,7 @@ export default class AgentRuns extends Command {
498
538
  if (eqIndex === -1)
499
539
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
500
540
  const key = f.slice(0, eqIndex);
501
- let value = f.slice(eqIndex + 1);
502
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
503
- if (opMatch) {
504
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
505
- }
506
- else {
507
- value = `\$eq:${value}`;
508
- }
541
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
509
542
  const filterKey = `filter.${key}`;
510
543
  if (filterKey in baseParams) {
511
544
  const existing = baseParams[filterKey];
@@ -682,6 +715,7 @@ export default class AgentRuns extends Command {
682
715
  }
683
716
  else if (flags.set && flags.set.length > 0) {
684
717
  const item = {};
718
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
685
719
  for (const pair of flags.set) {
686
720
  const eqIndex = pair.indexOf('=');
687
721
  if (eqIndex === -1)
@@ -692,6 +726,15 @@ export default class AgentRuns extends Command {
692
726
  value = JSON.parse(value);
693
727
  }
694
728
  catch { /* keep as string */ }
729
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
730
+ if (isArrayField && !Array.isArray(value)) {
731
+ if (typeof value === 'string') {
732
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
733
+ }
734
+ else if (value !== undefined && value !== null) {
735
+ value = [value];
736
+ }
737
+ }
695
738
  const keys = key.split('.');
696
739
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
697
740
  this.error(`Invalid --set key: ${key}`);
@@ -811,6 +854,21 @@ export default class AgentRuns extends Command {
811
854
  this.log('');
812
855
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
813
856
  }
857
+ parseFilterValue(value) {
858
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
859
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
860
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
861
+ // no operator prefix is present.
862
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
863
+ const m = value.match(opRe);
864
+ if (!m)
865
+ return `\$eq:${value}`;
866
+ const op = m[1];
867
+ const rest = value.slice(m[0].length);
868
+ if (rest === '')
869
+ return `\$${op}`;
870
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
871
+ }
814
872
  filterFields(data, fields) {
815
873
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
816
874
  const pick = (obj, keys) => {
@@ -30,6 +30,7 @@ export default class AuditLog extends Command {
30
30
  private runExport;
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
+ private parseFilterValue;
33
34
  private filterFields;
34
35
  private sanitizeOutput;
35
36
  private getColumns;
@@ -8,9 +8,9 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "find-entity", "find-workflow"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/audit-log', summary: 'Get audit log entries', pathParams: [], hasBody: false, isMultipart: false },
12
- 'find-entity': { method: 'GET', path: '/v1/audit-log/entity/{entityId}', summary: 'Get audit history for a specific entity', pathParams: ['entityId'], hasBody: false, isMultipart: false },
13
- 'find-workflow': { method: 'GET', path: '/v1/audit-log/workflow/{workflowId}', summary: 'Get audit log entries for a specific workflow', pathParams: ['workflowId'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/audit-log', summary: 'Get audit log entries', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'find-entity': { method: 'GET', path: '/v1/audit-log/entity/{entityId}', summary: 'Get audit history for a specific entity', pathParams: ['entityId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
+ 'find-workflow': { method: 'GET', path: '/v1/audit-log/workflow/{workflowId}', summary: 'Get audit log entries for a specific workflow', pathParams: ['workflowId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
14
  };
15
15
  export default class AuditLog extends Command {
16
16
  static description = 'Manage audit log';
@@ -75,11 +75,6 @@ export default class AuditLog extends Command {
75
75
  const endpoint = ENDPOINTS[args.action];
76
76
  if (!endpoint)
77
77
  this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
78
- const context = flags.context ? getContext(flags.context) : getCurrentContext();
79
- if (!context)
80
- this.error('No context configured. Run: aderis auth login --new');
81
- if (!context.tokens)
82
- this.error('Not logged in. Run: aderis auth login');
83
78
  let apiPath = endpoint.path;
84
79
  for (let i = 0; i < endpoint.pathParams.length; i++) {
85
80
  const param = endpoint.pathParams[i];
@@ -98,6 +93,11 @@ export default class AuditLog extends Command {
98
93
  }
99
94
  apiPath = apiPath.replace(`{${param}}`, String(value));
100
95
  }
96
+ const context = flags.context ? getContext(flags.context) : getCurrentContext();
97
+ if (!context)
98
+ this.error('No context configured. Run: aderis auth login --new');
99
+ if (!context.tokens)
100
+ this.error('Not logged in. Run: aderis auth login');
101
101
  const queryParams = {};
102
102
  if (flags.page)
103
103
  queryParams.page = flags.page;
@@ -124,15 +124,7 @@ export default class AuditLog extends Command {
124
124
  if (eqIndex === -1)
125
125
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
126
126
  const key = f.slice(0, eqIndex);
127
- let value = f.slice(eqIndex + 1);
128
- // Check for operator prefix (with or without $)
129
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
130
- if (opMatch) {
131
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
132
- }
133
- else {
134
- value = `\$eq:${value}`;
135
- }
127
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
136
128
  const filterKey = `filter.${key}`;
137
129
  if (filterKey in queryParams) {
138
130
  // Multiple filters on same field: collect as array
@@ -165,7 +157,9 @@ export default class AuditLog extends Command {
165
157
  data = form;
166
158
  }
167
159
  else if (endpoint.hasBody) {
168
- if (flags.file) {
160
+ // For binary-response endpoints, --file is the output path, not a JSON
161
+ // input file — skip the read-as-body branch.
162
+ if (flags.file && !endpoint.isBinaryResponse) {
169
163
  const fsModule = await import('node:fs');
170
164
  try {
171
165
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -187,6 +181,7 @@ export default class AuditLog extends Command {
187
181
  if (!data)
188
182
  data = {};
189
183
  const body = data;
184
+ const arrayFields = new Set(endpoint.bodyArrayFields);
190
185
  for (const pair of flags.set) {
191
186
  const eqIndex = pair.indexOf('=');
192
187
  if (eqIndex === -1)
@@ -198,6 +193,19 @@ export default class AuditLog extends Command {
198
193
  value = JSON.parse(value);
199
194
  }
200
195
  catch { /* keep as string */ }
196
+ // For top-level keys typed as array in the API schema, split bare
197
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
198
+ // wrap a lone scalar in a single-element array. Skips when --set
199
+ // already provided a JSON array.
200
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
201
+ if (isArrayField && !Array.isArray(value)) {
202
+ if (typeof value === 'string') {
203
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
204
+ }
205
+ else if (value !== undefined && value !== null) {
206
+ value = [value];
207
+ }
208
+ }
201
209
  // Support nested keys like "address.city"
202
210
  const keys = key.split('.');
203
211
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -248,6 +256,26 @@ export default class AuditLog extends Command {
248
256
  process.stderr.write('\n');
249
257
  }
250
258
  try {
259
+ // Binary response: download to file
260
+ if (endpoint.isBinaryResponse) {
261
+ if (!flags.file) {
262
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
263
+ }
264
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
265
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
266
+ data,
267
+ contextName: flags.context,
268
+ binary: true,
269
+ });
270
+ const fsModule = await import('node:fs');
271
+ const binData = binResponse.data;
272
+ const buffer = Buffer.isBuffer(binData)
273
+ ? binData
274
+ : Buffer.from(binData);
275
+ fsModule.writeFileSync(flags.file, buffer);
276
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
277
+ return;
278
+ }
251
279
  const response = await apiRequest(endpoint.method, apiPath, {
252
280
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
253
281
  data,
@@ -338,6 +366,7 @@ export default class AuditLog extends Command {
338
366
  }
339
367
  else if (flags.set && flags.set.length > 0) {
340
368
  const item = {};
369
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
341
370
  for (const pair of flags.set) {
342
371
  const eqIndex = pair.indexOf('=');
343
372
  if (eqIndex === -1)
@@ -348,6 +377,15 @@ export default class AuditLog extends Command {
348
377
  value = JSON.parse(value);
349
378
  }
350
379
  catch { /* keep as string */ }
380
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
381
+ if (isArrayField && !Array.isArray(value)) {
382
+ if (typeof value === 'string') {
383
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
384
+ }
385
+ else if (value !== undefined && value !== null) {
386
+ value = [value];
387
+ }
388
+ }
351
389
  const keys = key.split('.');
352
390
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
353
391
  this.error(`Invalid --set key: ${key}`);
@@ -497,14 +535,7 @@ export default class AuditLog extends Command {
497
535
  if (eqIndex === -1)
498
536
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
499
537
  const key = f.slice(0, eqIndex);
500
- let value = f.slice(eqIndex + 1);
501
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
502
- if (opMatch) {
503
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
504
- }
505
- else {
506
- value = `\$eq:${value}`;
507
- }
538
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
508
539
  const filterKey = `filter.${key}`;
509
540
  if (filterKey in baseParams) {
510
541
  const existing = baseParams[filterKey];
@@ -681,6 +712,7 @@ export default class AuditLog extends Command {
681
712
  }
682
713
  else if (flags.set && flags.set.length > 0) {
683
714
  const item = {};
715
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
684
716
  for (const pair of flags.set) {
685
717
  const eqIndex = pair.indexOf('=');
686
718
  if (eqIndex === -1)
@@ -691,6 +723,15 @@ export default class AuditLog extends Command {
691
723
  value = JSON.parse(value);
692
724
  }
693
725
  catch { /* keep as string */ }
726
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
727
+ if (isArrayField && !Array.isArray(value)) {
728
+ if (typeof value === 'string') {
729
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
730
+ }
731
+ else if (value !== undefined && value !== null) {
732
+ value = [value];
733
+ }
734
+ }
694
735
  const keys = key.split('.');
695
736
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
696
737
  this.error(`Invalid --set key: ${key}`);
@@ -810,6 +851,21 @@ export default class AuditLog extends Command {
810
851
  this.log('');
811
852
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
812
853
  }
854
+ parseFilterValue(value) {
855
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
856
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
857
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
858
+ // no operator prefix is present.
859
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
860
+ const m = value.match(opRe);
861
+ if (!m)
862
+ return `\$eq:${value}`;
863
+ const op = m[1];
864
+ const rest = value.slice(m[0].length);
865
+ if (rest === '')
866
+ return `\$${op}`;
867
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
868
+ }
813
869
  filterFields(data, fields) {
814
870
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
815
871
  const pick = (obj, keys) => {
@@ -90,8 +90,7 @@ export default class AuthLogin extends Command {
90
90
  });
91
91
  }
92
92
  // Get environment
93
- let environment;
94
- environment = flags.environment ? flags.environment : (await select({
93
+ const environment = flags.environment ? flags.environment : (await select({
95
94
  choices: [
96
95
  { description: 'Development environment', name: 'dev', value: 'dev' },
97
96
  { description: 'Test/staging environment', name: 'test', value: 'test' },
@@ -13,7 +13,7 @@ export default class AuthTenant extends Command {
13
13
  run(): Promise<void>;
14
14
  private interactiveSwitch;
15
15
  private listTenants;
16
+ private resolveTenantName;
16
17
  private showCurrentTenant;
17
18
  private switchTenant;
18
- private resolveTenantName;
19
19
  }
@@ -135,6 +135,17 @@ export default class AuthTenant extends Command {
135
135
  }
136
136
  this.log('\n* = current tenant');
137
137
  }
138
+ async resolveTenantName(contextName, tenantId) {
139
+ if (!tenantId)
140
+ return undefined;
141
+ try {
142
+ const tenants = await listTenants(contextName);
143
+ return tenants.find((t) => t.tenantId === tenantId)?.name;
144
+ }
145
+ catch {
146
+ return undefined;
147
+ }
148
+ }
138
149
  async showCurrentTenant(contextName, asJson) {
139
150
  const current = await getCurrentTenant(contextName);
140
151
  if (!current) {
@@ -170,15 +181,4 @@ export default class AuthTenant extends Command {
170
181
  throw error;
171
182
  }
172
183
  }
173
- async resolveTenantName(contextName, tenantId) {
174
- if (!tenantId)
175
- return undefined;
176
- try {
177
- const tenants = await listTenants(contextName);
178
- return tenants.find((t) => t.tenantId === tenantId)?.name;
179
- }
180
- catch {
181
- return undefined;
182
- }
183
- }
184
184
  }
@@ -841,7 +841,7 @@ export default class Auth extends Command {
841
841
  const result = {};
842
842
  // Collect UUIDs from this level to pass to children
843
843
  const currentUuids = new Set(parentUuids);
844
- for (const [key, value] of Object.entries(input)) {
844
+ for (const [, value] of Object.entries(input)) {
845
845
  if (typeof value === 'string' && uuidRegex.test(value)) {
846
846
  currentUuids.add(value);
847
847
  }
@@ -32,6 +32,7 @@ export default class BankAccounts extends Command {
32
32
  private runExport;
33
33
  private runCamt053Import;
34
34
  private runNestedImport;
35
+ private parseFilterValue;
35
36
  private filterFields;
36
37
  private sanitizeOutput;
37
38
  private getColumns;