@aderis-erp/cli 1.0.15 → 1.1.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.
Files changed (182) hide show
  1. package/README.md +30 -8
  2. package/dist/commands/access-groups.d.ts +0 -1
  3. package/dist/commands/access-groups.js +54 -78
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +51 -75
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +80 -94
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +59 -83
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +58 -82
  12. package/dist/commands/agent-runs.d.ts +39 -0
  13. package/dist/commands/agent-runs.js +1155 -0
  14. package/dist/commands/analytics.d.ts +0 -1
  15. package/dist/commands/analytics.js +52 -76
  16. package/dist/commands/{text-templates.d.ts → api-keys.d.ts} +1 -2
  17. package/dist/commands/api-keys.js +1150 -0
  18. package/dist/commands/async-jobs.d.ts +39 -0
  19. package/dist/commands/async-jobs.js +1158 -0
  20. package/dist/commands/audit-log.d.ts +0 -1
  21. package/dist/commands/audit-log.js +51 -75
  22. package/dist/commands/auth/login.d.ts +9 -0
  23. package/dist/commands/auth/login.js +73 -20
  24. package/dist/commands/auth/logout.d.ts +4 -0
  25. package/dist/commands/auth/logout.js +4 -0
  26. package/dist/commands/auth/status.js +26 -17
  27. package/dist/commands/auth/tenant.js +3 -6
  28. package/dist/commands/auth.d.ts +0 -1
  29. package/dist/commands/auth.js +5 -34
  30. package/dist/commands/bank-accounts.d.ts +0 -1
  31. package/dist/commands/bank-accounts.js +68 -91
  32. package/dist/commands/bank-integrations.d.ts +1 -1
  33. package/dist/commands/bank-integrations.js +71 -92
  34. package/dist/commands/business-partners.d.ts +1 -1
  35. package/dist/commands/business-partners.js +84 -100
  36. package/dist/commands/chat.d.ts +0 -1
  37. package/dist/commands/chat.js +57 -80
  38. package/dist/commands/config/add.d.ts +3 -0
  39. package/dist/commands/config/add.js +34 -16
  40. package/dist/commands/config/current.js +3 -0
  41. package/dist/commands/config/list.js +3 -0
  42. package/dist/commands/credit-card-accounts.d.ts +1 -1
  43. package/dist/commands/credit-card-accounts.js +72 -90
  44. package/dist/commands/delivery-notes.d.ts +0 -1
  45. package/dist/commands/delivery-notes.js +83 -105
  46. package/dist/commands/dunning-configs.d.ts +0 -1
  47. package/dist/commands/dunning-configs.js +54 -78
  48. package/dist/commands/dunning-reminders.d.ts +0 -1
  49. package/dist/commands/dunning-reminders.js +57 -81
  50. package/dist/commands/dunning-runs.d.ts +0 -1
  51. package/dist/commands/dunning-runs.js +50 -74
  52. package/dist/commands/email-transactions.d.ts +0 -1
  53. package/dist/commands/email-transactions.js +52 -76
  54. package/dist/commands/employees.d.ts +0 -1
  55. package/dist/commands/employees.js +81 -105
  56. package/dist/commands/entity-configurations.d.ts +0 -1
  57. package/dist/commands/entity-configurations.js +52 -76
  58. package/dist/commands/event-processor-mappings.d.ts +0 -1
  59. package/dist/commands/event-processor-mappings.js +54 -78
  60. package/dist/commands/files.d.ts +0 -1
  61. package/dist/commands/files.js +61 -85
  62. package/dist/commands/health.d.ts +0 -1
  63. package/dist/commands/health.js +54 -78
  64. package/dist/commands/imports.d.ts +39 -0
  65. package/dist/commands/imports.js +1151 -0
  66. package/dist/commands/inbox.d.ts +0 -1
  67. package/dist/commands/inbox.js +57 -80
  68. package/dist/commands/integration-identities.d.ts +40 -0
  69. package/dist/commands/integration-identities.js +1151 -0
  70. package/dist/commands/integrations.d.ts +0 -1
  71. package/dist/commands/integrations.js +52 -76
  72. package/dist/commands/item-categories.d.ts +0 -1
  73. package/dist/commands/item-categories.js +54 -78
  74. package/dist/commands/items.d.ts +0 -1
  75. package/dist/commands/items.js +54 -78
  76. package/dist/commands/journal-lines.d.ts +0 -1
  77. package/dist/commands/journal-lines.js +50 -74
  78. package/dist/commands/journals.d.ts +1 -1
  79. package/dist/commands/journals.js +75 -96
  80. package/dist/commands/ledgers.d.ts +3 -2
  81. package/dist/commands/ledgers.js +100 -95
  82. package/dist/commands/my-account.d.ts +39 -0
  83. package/dist/commands/my-account.js +1160 -0
  84. package/dist/commands/office-locations.d.ts +0 -1
  85. package/dist/commands/office-locations.js +54 -78
  86. package/dist/commands/payment-methods.d.ts +0 -1
  87. package/dist/commands/payment-methods.js +59 -83
  88. package/dist/commands/payment-runs.d.ts +0 -1
  89. package/dist/commands/payment-runs.js +66 -90
  90. package/dist/commands/payment-terms.d.ts +0 -1
  91. package/dist/commands/payment-terms.js +55 -79
  92. package/dist/commands/payments.d.ts +0 -1
  93. package/dist/commands/payments.js +56 -80
  94. package/dist/commands/payroll-runs.d.ts +43 -0
  95. package/dist/commands/payroll-runs.js +1188 -0
  96. package/dist/commands/permissions.d.ts +39 -0
  97. package/dist/commands/permissions.js +1146 -0
  98. package/dist/commands/price-lists.d.ts +0 -1
  99. package/dist/commands/price-lists.js +59 -83
  100. package/dist/commands/processor-mappings.d.ts +0 -1
  101. package/dist/commands/processor-mappings.js +53 -77
  102. package/dist/commands/processors.d.ts +0 -1
  103. package/dist/commands/processors.js +60 -85
  104. package/dist/commands/purchase-order-types.d.ts +0 -1
  105. package/dist/commands/purchase-order-types.js +54 -78
  106. package/dist/commands/purchase-orders.d.ts +0 -1
  107. package/dist/commands/purchase-orders.js +74 -97
  108. package/dist/commands/reports.d.ts +0 -1
  109. package/dist/commands/reports.js +54 -78
  110. package/dist/commands/roles.d.ts +39 -0
  111. package/dist/commands/{text-templates.js → roles.js} +64 -90
  112. package/dist/commands/salary-types.d.ts +0 -1
  113. package/dist/commands/salary-types.js +54 -78
  114. package/dist/commands/sales-invoices.d.ts +0 -1
  115. package/dist/commands/sales-invoices.js +90 -110
  116. package/dist/commands/sales-orders.d.ts +0 -1
  117. package/dist/commands/sales-orders.js +83 -105
  118. package/dist/commands/saved-views.d.ts +0 -1
  119. package/dist/commands/saved-views.js +52 -76
  120. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  121. package/dist/commands/scheduled-processor-mappings.js +54 -78
  122. package/dist/commands/schedules.d.ts +0 -1
  123. package/dist/commands/schedules.js +58 -82
  124. package/dist/commands/switch.js +4 -4
  125. package/dist/commands/tag-configurations.d.ts +0 -1
  126. package/dist/commands/tag-configurations.js +56 -79
  127. package/dist/commands/tags.d.ts +0 -1
  128. package/dist/commands/tags.js +54 -78
  129. package/dist/commands/tax-codes.d.ts +0 -1
  130. package/dist/commands/tax-codes.js +57 -81
  131. package/dist/commands/tax-matrix.d.ts +0 -1
  132. package/dist/commands/tax-matrix.js +54 -78
  133. package/dist/commands/templates.d.ts +39 -0
  134. package/dist/commands/templates.js +1157 -0
  135. package/dist/commands/tenants.d.ts +0 -1
  136. package/dist/commands/tenants.js +58 -81
  137. package/dist/commands/tools.d.ts +0 -1
  138. package/dist/commands/tools.js +51 -75
  139. package/dist/commands/users.d.ts +42 -0
  140. package/dist/commands/users.js +1174 -0
  141. package/dist/commands/vat-declarations.d.ts +3 -1
  142. package/dist/commands/vat-declarations.js +78 -93
  143. package/dist/commands/vendor-invoices.d.ts +0 -1
  144. package/dist/commands/vendor-invoices.js +87 -110
  145. package/dist/commands/webhooks.d.ts +0 -1
  146. package/dist/commands/webhooks.js +56 -80
  147. package/dist/commands/workflows.d.ts +0 -1
  148. package/dist/commands/workflows.js +52 -76
  149. package/dist/lib/api-client.d.ts +6 -0
  150. package/dist/lib/api-client.js +16 -6
  151. package/dist/lib/auth/constants.d.ts +11 -0
  152. package/dist/lib/auth/constants.js +11 -0
  153. package/dist/lib/auth/errors.d.ts +17 -0
  154. package/dist/lib/auth/errors.js +22 -0
  155. package/dist/lib/auth/flow.d.ts +27 -0
  156. package/dist/lib/auth/flow.js +163 -0
  157. package/dist/lib/auth/guards.d.ts +5 -0
  158. package/dist/lib/auth/guards.js +14 -0
  159. package/dist/lib/auth/http.d.ts +16 -0
  160. package/dist/lib/auth/http.js +49 -0
  161. package/dist/lib/auth/index.d.ts +49 -0
  162. package/dist/lib/auth/index.js +228 -0
  163. package/dist/lib/auth/jwks.d.ts +19 -0
  164. package/dist/lib/auth/jwks.js +112 -0
  165. package/dist/lib/auth/keycloak-provider.d.ts +91 -0
  166. package/dist/lib/auth/keycloak-provider.js +244 -0
  167. package/dist/lib/auth/pages.d.ts +25 -0
  168. package/dist/lib/auth/pages.js +123 -0
  169. package/dist/lib/auth/pkce.d.ts +11 -0
  170. package/dist/lib/auth/pkce.js +17 -0
  171. package/dist/lib/auth/tokens.d.ts +59 -0
  172. package/dist/lib/auth/tokens.js +105 -0
  173. package/dist/lib/auth/types.d.ts +61 -0
  174. package/dist/lib/auth/types.js +1 -0
  175. package/dist/lib/auth/validated-url.d.ts +6 -0
  176. package/dist/lib/auth/validated-url.js +53 -0
  177. package/dist/lib/config.d.ts +13 -3
  178. package/dist/lib/config.js +24 -19
  179. package/oclif.manifest.json +2447 -641
  180. package/package.json +5 -4
  181. package/dist/lib/auth.d.ts +0 -43
  182. package/dist/lib/auth.js +0 -610
@@ -1,5 +1,5 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
@@ -8,14 +8,14 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "tax-codes-standard-rate", "get-by-country-rate", "get-by-code-at-date", "get", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/tax/tax-codes', summary: 'Get all tax codes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/tax/tax-codes', summary: 'Create a new tax code', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['directions'] },
13
- 'tax-codes-standard-rate': { method: 'GET', path: '/v1/tax/tax-codes/standard-rate', summary: 'Get the standard-rate tax code for a country at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'get-by-country-rate': { method: 'GET', path: '/v1/tax/tax-codes/by-country-rate', summary: 'Get tax code by country and rate at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- 'get-by-code-at-date': { method: 'GET', path: '/v1/tax/tax-codes/by-code-at-date', summary: 'Get tax code by code at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'get': { method: 'GET', path: '/v1/tax/tax-codes/{id}', summary: 'Get a tax code by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/tax/tax-codes/{id}', summary: 'Update a tax code', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['directions'] },
18
- 'delete': { method: 'DELETE', path: '/v1/tax/tax-codes/{id}', summary: 'Delete a tax code', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'list': { method: 'GET', path: '/v1/tax/tax-codes', summary: 'Get all tax codes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/tax/tax-codes', summary: 'Create a new tax code', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['directions'] },
13
+ 'tax-codes-standard-rate': { method: 'GET', path: '/v1/tax/tax-codes/standard-rate', summary: 'Get the standard-rate tax code for a country at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'get-by-country-rate': { method: 'GET', path: '/v1/tax/tax-codes/by-country-rate', summary: 'Get tax code by country and rate at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'get-by-code-at-date': { method: 'GET', path: '/v1/tax/tax-codes/by-code-at-date', summary: 'Get tax code by code at a specific date', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
+ 'get': { method: 'GET', path: '/v1/tax/tax-codes/{id}', summary: 'Get a tax code by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
+ 'update': { method: 'PATCH', path: '/v1/tax/tax-codes/{id}', summary: 'Update a tax code', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['directions'] },
18
+ 'delete': { method: 'DELETE', path: '/v1/tax/tax-codes/{id}', summary: 'Delete a tax code', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
19
  };
20
20
  export default class TaxCodes extends Command {
21
21
  static description = 'Manage tax codes';
@@ -46,7 +46,7 @@ export default class TaxCodes extends Command {
46
46
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
47
47
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
48
48
  query: Flags.string({ description: 'Additional query params as JSON' }),
49
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
49
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
50
50
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
51
51
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
52
52
  };
@@ -118,6 +118,8 @@ export default class TaxCodes extends Command {
118
118
  queryParams.search = flags.search;
119
119
  if (flags['sort-by'])
120
120
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
121
+ if (flags.fields)
122
+ queryParams.fields = flags.fields;
121
123
  if (flags.query) {
122
124
  try {
123
125
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -265,10 +267,11 @@ export default class TaxCodes extends Command {
265
267
  process.stderr.write('\n');
266
268
  }
267
269
  try {
268
- // Binary response: download to file
270
+ // Binary response: to -f / --file, or to stdout when it is redirected.
269
271
  if (endpoint.isBinaryResponse) {
270
- if (!flags.file) {
271
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
272
+ const toStdout = !flags.file;
273
+ if (toStdout && process.stdout.isTTY) {
274
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
272
275
  }
273
276
  const binResponse = await apiRequest(endpoint.method, apiPath, {
274
277
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -276,24 +279,55 @@ export default class TaxCodes extends Command {
276
279
  contextName: flags.context,
277
280
  binary: true,
278
281
  });
279
- const fsModule = await import('node:fs');
280
282
  const binData = binResponse.data;
281
283
  const buffer = Buffer.isBuffer(binData)
282
284
  ? binData
283
285
  : Buffer.from(binData);
286
+ if (toStdout) {
287
+ // Nothing else may reach stdout here — it is the file.
288
+ process.stdout.write(buffer);
289
+ return;
290
+ }
291
+ const fsModule = await import('node:fs');
284
292
  fsModule.writeFileSync(flags.file, buffer);
285
293
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
286
294
  return;
287
295
  }
288
- const response = await apiRequest(endpoint.method, apiPath, {
289
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
290
- data,
291
- contextName: flags.context,
292
- acceptAny: true,
293
- });
296
+ let response;
297
+ if (endpoint.hasBinaryVariant) {
298
+ // This route answers JSON or a file depending on the request body, so the
299
+ // bytes are read first and the response's own content-type decides. Asking
300
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
301
+ const raw = await apiRequest(endpoint.method, apiPath, {
302
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
303
+ data,
304
+ contextName: flags.context,
305
+ binary: true,
306
+ });
307
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
308
+ const rawData = raw.data;
309
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
310
+ if (!isTextualMediaType(rawType)) {
311
+ if (process.stdout.isTTY) {
312
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
313
+ }
314
+ // Nothing else may reach stdout here — it is the file.
315
+ process.stdout.write(buffer);
316
+ return;
317
+ }
318
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
319
+ }
320
+ else {
321
+ response = await apiRequest(endpoint.method, apiPath, {
322
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
323
+ data,
324
+ contextName: flags.context,
325
+ acceptAny: true,
326
+ });
327
+ }
294
328
  // Check content-type for non-JSON responses (XML, text, etc.)
295
329
  const contentType = (response.headers['content-type'] || '').toLowerCase();
296
- if (contentType.includes('xml') || contentType.includes('text/')) {
330
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
297
331
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
298
332
  this.log(String(response.data));
299
333
  return;
@@ -320,11 +354,6 @@ export default class TaxCodes extends Command {
320
354
  output = obj[arrayKeys[0]];
321
355
  }
322
356
  }
323
- // Filter fields if --fields flag is provided
324
- if (flags.fields) {
325
- const fieldList = flags.fields.split(',').map(f => f.trim());
326
- output = this.filterFields(output, fieldList);
327
- }
328
357
  // Sanitize output if requested
329
358
  if (flags.sanitize) {
330
359
  output = this.sanitizeOutput(output);
@@ -579,6 +608,8 @@ export default class TaxCodes extends Command {
579
608
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
580
609
  if (flags.include)
581
610
  baseParams.include = flags.include;
611
+ if (flags.fields)
612
+ baseParams.fields = flags.fields;
582
613
  if (flags.query) {
583
614
  try {
584
615
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -633,12 +664,7 @@ export default class TaxCodes extends Command {
633
664
  }
634
665
  }
635
666
  process.stderr.write(' done\n');
636
- // Filter fields if specified
637
667
  let output = allItems;
638
- if (flags.fields) {
639
- const fieldList = flags.fields.split(',').map(f => f.trim());
640
- output = this.filterFields(allItems, fieldList);
641
- }
642
668
  // Sanitize output if requested
643
669
  if (flags.sanitize) {
644
670
  output = this.sanitizeOutput(output);
@@ -967,56 +993,6 @@ export default class TaxCodes extends Command {
967
993
  return `\$${op}`;
968
994
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
969
995
  }
970
- filterFields(data, fields) {
971
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
972
- const pick = (obj, keys) => {
973
- // Group keys by their first segment so sibling sub-paths
974
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
975
- // and don't clobber each other on merge.
976
- const directKeys = new Set();
977
- const nestedKeys = new Map();
978
- for (const key of keys) {
979
- if (key.split('.').some(isUnsafeKey))
980
- continue;
981
- if (key.includes('.')) {
982
- const [first, ...rest] = key.split('.');
983
- if (isUnsafeKey(first))
984
- continue;
985
- const list = nestedKeys.get(first);
986
- if (list) {
987
- list.push(rest.join('.'));
988
- }
989
- else {
990
- nestedKeys.set(first, [rest.join('.')]);
991
- }
992
- }
993
- else {
994
- directKeys.add(key);
995
- }
996
- }
997
- const result = {};
998
- for (const key of directKeys) {
999
- if (key in obj)
1000
- result[key] = obj[key];
1001
- }
1002
- for (const [first, restKeys] of nestedKeys) {
1003
- const val = obj[first];
1004
- if (!val || typeof val !== 'object')
1005
- continue;
1006
- if (Array.isArray(val)) {
1007
- result[first] = val.map(item => pick(item, restKeys));
1008
- }
1009
- else {
1010
- result[first] = pick(val, restKeys);
1011
- }
1012
- }
1013
- return result;
1014
- };
1015
- if (Array.isArray(data)) {
1016
- return data.map(item => pick(item, fields));
1017
- }
1018
- return pick(data, fields);
1019
- }
1020
996
  sanitizeOutput(data) {
1021
997
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1022
998
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -31,7 +31,6 @@ export default class TaxMatrix extends Command {
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
33
  private parseFilterValue;
34
- private filterFields;
35
34
  private sanitizeOutput;
36
35
  private getColumns;
37
36
  private getValue;
@@ -1,5 +1,5 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/tax/tax-matrix', summary: 'Get all tax matrix mappings with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/tax/tax-matrix', summary: 'Create a new tax matrix mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
- 'get': { method: 'GET', path: '/v1/tax/tax-matrix/{id}', summary: 'Get a tax matrix mapping by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'update': { method: 'PATCH', path: '/v1/tax/tax-matrix/{id}', summary: 'Update a tax matrix mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- 'delete': { method: 'DELETE', path: '/v1/tax/tax-matrix/{id}', summary: 'Delete a tax matrix mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'list': { method: 'GET', path: '/v1/tax/tax-matrix', summary: 'Get all tax matrix mappings with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/tax/tax-matrix', summary: 'Create a new tax matrix mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'get': { method: 'GET', path: '/v1/tax/tax-matrix/{id}', summary: 'Get a tax matrix mapping by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/tax/tax-matrix/{id}', summary: 'Update a tax matrix mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'delete': { method: 'DELETE', path: '/v1/tax/tax-matrix/{id}', summary: 'Delete a tax matrix mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
16
  };
17
17
  export default class TaxMatrix extends Command {
18
18
  static description = 'Manage tax matrix';
@@ -43,7 +43,7 @@ export default class TaxMatrix extends Command {
43
43
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
44
44
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
45
45
  query: Flags.string({ description: 'Additional query params as JSON' }),
46
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
46
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
47
47
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
48
48
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
49
49
  };
@@ -115,6 +115,8 @@ export default class TaxMatrix extends Command {
115
115
  queryParams.search = flags.search;
116
116
  if (flags['sort-by'])
117
117
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
118
+ if (flags.fields)
119
+ queryParams.fields = flags.fields;
118
120
  if (flags.query) {
119
121
  try {
120
122
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -262,10 +264,11 @@ export default class TaxMatrix extends Command {
262
264
  process.stderr.write('\n');
263
265
  }
264
266
  try {
265
- // Binary response: download to file
267
+ // Binary response: to -f / --file, or to stdout when it is redirected.
266
268
  if (endpoint.isBinaryResponse) {
267
- if (!flags.file) {
268
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
269
+ const toStdout = !flags.file;
270
+ if (toStdout && process.stdout.isTTY) {
271
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
269
272
  }
270
273
  const binResponse = await apiRequest(endpoint.method, apiPath, {
271
274
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -273,24 +276,55 @@ export default class TaxMatrix extends Command {
273
276
  contextName: flags.context,
274
277
  binary: true,
275
278
  });
276
- const fsModule = await import('node:fs');
277
279
  const binData = binResponse.data;
278
280
  const buffer = Buffer.isBuffer(binData)
279
281
  ? binData
280
282
  : Buffer.from(binData);
283
+ if (toStdout) {
284
+ // Nothing else may reach stdout here — it is the file.
285
+ process.stdout.write(buffer);
286
+ return;
287
+ }
288
+ const fsModule = await import('node:fs');
281
289
  fsModule.writeFileSync(flags.file, buffer);
282
290
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
283
291
  return;
284
292
  }
285
- const response = await apiRequest(endpoint.method, apiPath, {
286
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
287
- data,
288
- contextName: flags.context,
289
- acceptAny: true,
290
- });
293
+ let response;
294
+ if (endpoint.hasBinaryVariant) {
295
+ // This route answers JSON or a file depending on the request body, so the
296
+ // bytes are read first and the response's own content-type decides. Asking
297
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
298
+ const raw = await apiRequest(endpoint.method, apiPath, {
299
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
300
+ data,
301
+ contextName: flags.context,
302
+ binary: true,
303
+ });
304
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
305
+ const rawData = raw.data;
306
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
307
+ if (!isTextualMediaType(rawType)) {
308
+ if (process.stdout.isTTY) {
309
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
310
+ }
311
+ // Nothing else may reach stdout here — it is the file.
312
+ process.stdout.write(buffer);
313
+ return;
314
+ }
315
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
316
+ }
317
+ else {
318
+ response = await apiRequest(endpoint.method, apiPath, {
319
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
320
+ data,
321
+ contextName: flags.context,
322
+ acceptAny: true,
323
+ });
324
+ }
291
325
  // Check content-type for non-JSON responses (XML, text, etc.)
292
326
  const contentType = (response.headers['content-type'] || '').toLowerCase();
293
- if (contentType.includes('xml') || contentType.includes('text/')) {
327
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
294
328
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
295
329
  this.log(String(response.data));
296
330
  return;
@@ -317,11 +351,6 @@ export default class TaxMatrix extends Command {
317
351
  output = obj[arrayKeys[0]];
318
352
  }
319
353
  }
320
- // Filter fields if --fields flag is provided
321
- if (flags.fields) {
322
- const fieldList = flags.fields.split(',').map(f => f.trim());
323
- output = this.filterFields(output, fieldList);
324
- }
325
354
  // Sanitize output if requested
326
355
  if (flags.sanitize) {
327
356
  output = this.sanitizeOutput(output);
@@ -576,6 +605,8 @@ export default class TaxMatrix extends Command {
576
605
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
577
606
  if (flags.include)
578
607
  baseParams.include = flags.include;
608
+ if (flags.fields)
609
+ baseParams.fields = flags.fields;
579
610
  if (flags.query) {
580
611
  try {
581
612
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -630,12 +661,7 @@ export default class TaxMatrix extends Command {
630
661
  }
631
662
  }
632
663
  process.stderr.write(' done\n');
633
- // Filter fields if specified
634
664
  let output = allItems;
635
- if (flags.fields) {
636
- const fieldList = flags.fields.split(',').map(f => f.trim());
637
- output = this.filterFields(allItems, fieldList);
638
- }
639
665
  // Sanitize output if requested
640
666
  if (flags.sanitize) {
641
667
  output = this.sanitizeOutput(output);
@@ -964,56 +990,6 @@ export default class TaxMatrix extends Command {
964
990
  return `\$${op}`;
965
991
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
966
992
  }
967
- filterFields(data, fields) {
968
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
969
- const pick = (obj, keys) => {
970
- // Group keys by their first segment so sibling sub-paths
971
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
972
- // and don't clobber each other on merge.
973
- const directKeys = new Set();
974
- const nestedKeys = new Map();
975
- for (const key of keys) {
976
- if (key.split('.').some(isUnsafeKey))
977
- continue;
978
- if (key.includes('.')) {
979
- const [first, ...rest] = key.split('.');
980
- if (isUnsafeKey(first))
981
- continue;
982
- const list = nestedKeys.get(first);
983
- if (list) {
984
- list.push(rest.join('.'));
985
- }
986
- else {
987
- nestedKeys.set(first, [rest.join('.')]);
988
- }
989
- }
990
- else {
991
- directKeys.add(key);
992
- }
993
- }
994
- const result = {};
995
- for (const key of directKeys) {
996
- if (key in obj)
997
- result[key] = obj[key];
998
- }
999
- for (const [first, restKeys] of nestedKeys) {
1000
- const val = obj[first];
1001
- if (!val || typeof val !== 'object')
1002
- continue;
1003
- if (Array.isArray(val)) {
1004
- result[first] = val.map(item => pick(item, restKeys));
1005
- }
1006
- else {
1007
- result[first] = pick(val, restKeys);
1008
- }
1009
- }
1010
- return result;
1011
- };
1012
- if (Array.isArray(data)) {
1013
- return data.map(item => pick(item, fields));
1014
- }
1015
- return pick(data, fields);
1016
- }
1017
993
  sanitizeOutput(data) {
1018
994
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1019
995
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -0,0 +1,39 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Templates extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ };
28
+ run(): Promise<void>;
29
+ private runImport;
30
+ private runExport;
31
+ private runCamt053Import;
32
+ private runNestedImport;
33
+ private parseFilterValue;
34
+ private sanitizeOutput;
35
+ private getColumns;
36
+ private getValue;
37
+ private printTable;
38
+ private printCsv;
39
+ }