@aderis-erp/cli 1.0.14 → 1.0.17

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 (167) 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 +22 -78
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +22 -78
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +28 -82
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +24 -80
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +24 -79
  12. package/dist/commands/analytics.d.ts +39 -0
  13. package/dist/commands/analytics.js +1113 -0
  14. package/dist/commands/{documents.d.ts → api-keys.d.ts} +1 -2
  15. package/dist/commands/api-keys.js +1117 -0
  16. package/dist/commands/audit-log.d.ts +0 -1
  17. package/dist/commands/audit-log.js +22 -78
  18. package/dist/commands/auth/login.d.ts +9 -0
  19. package/dist/commands/auth/login.js +83 -21
  20. package/dist/commands/auth/logout.d.ts +6 -0
  21. package/dist/commands/auth/logout.js +23 -6
  22. package/dist/commands/auth/status.js +46 -23
  23. package/dist/commands/auth/tenant.js +15 -9
  24. package/dist/commands/auth.d.ts +0 -1
  25. package/dist/commands/auth.js +37 -51
  26. package/dist/commands/bank-accounts.d.ts +0 -1
  27. package/dist/commands/bank-accounts.js +22 -78
  28. package/dist/commands/bank-integrations.d.ts +0 -1
  29. package/dist/commands/bank-integrations.js +22 -78
  30. package/dist/commands/business-partners.d.ts +1 -1
  31. package/dist/commands/business-partners.js +38 -84
  32. package/dist/commands/chat.d.ts +0 -1
  33. package/dist/commands/chat.js +22 -78
  34. package/dist/commands/config/add.d.ts +12 -0
  35. package/dist/commands/config/add.js +97 -17
  36. package/dist/commands/config/current.js +12 -4
  37. package/dist/commands/config/list.js +12 -4
  38. package/dist/commands/config/set-api-key.d.ts +10 -0
  39. package/dist/commands/config/set-api-key.js +49 -0
  40. package/dist/commands/config/use.js +3 -4
  41. package/dist/commands/credit-card-accounts.d.ts +0 -1
  42. package/dist/commands/credit-card-accounts.js +22 -78
  43. package/dist/commands/delivery-notes.d.ts +0 -1
  44. package/dist/commands/delivery-notes.js +29 -84
  45. package/dist/commands/dunning-configs.d.ts +0 -1
  46. package/dist/commands/dunning-configs.js +22 -78
  47. package/dist/commands/dunning-reminders.d.ts +0 -1
  48. package/dist/commands/dunning-reminders.js +22 -78
  49. package/dist/commands/dunning-runs.d.ts +0 -1
  50. package/dist/commands/dunning-runs.js +22 -78
  51. package/dist/commands/email-transactions.d.ts +0 -1
  52. package/dist/commands/email-transactions.js +22 -78
  53. package/dist/commands/employees.d.ts +0 -1
  54. package/dist/commands/employees.js +24 -80
  55. package/dist/commands/entity-configurations.d.ts +0 -1
  56. package/dist/commands/entity-configurations.js +22 -78
  57. package/dist/commands/event-processor-mappings.d.ts +0 -1
  58. package/dist/commands/event-processor-mappings.js +22 -78
  59. package/dist/commands/{files-internal.d.ts → files.d.ts} +1 -2
  60. package/dist/commands/{files-internal.js → files.js} +40 -90
  61. package/dist/commands/health.d.ts +0 -1
  62. package/dist/commands/health.js +22 -78
  63. package/dist/commands/inbox.d.ts +0 -1
  64. package/dist/commands/inbox.js +22 -78
  65. package/dist/commands/integrations.d.ts +0 -1
  66. package/dist/commands/integrations.js +22 -78
  67. package/dist/commands/item-categories.d.ts +0 -1
  68. package/dist/commands/item-categories.js +22 -78
  69. package/dist/commands/items.d.ts +0 -1
  70. package/dist/commands/items.js +24 -80
  71. package/dist/commands/journal-lines.d.ts +0 -1
  72. package/dist/commands/journal-lines.js +22 -78
  73. package/dist/commands/journals.d.ts +1 -1
  74. package/dist/commands/journals.js +29 -81
  75. package/dist/commands/ledgers.d.ts +0 -1
  76. package/dist/commands/ledgers.js +26 -79
  77. package/dist/commands/nango-integrations.d.ts +39 -0
  78. package/dist/commands/nango-integrations.js +1115 -0
  79. package/dist/commands/office-locations.d.ts +0 -1
  80. package/dist/commands/office-locations.js +22 -78
  81. package/dist/commands/payment-methods.d.ts +0 -1
  82. package/dist/commands/payment-methods.js +22 -78
  83. package/dist/commands/payment-runs.d.ts +0 -1
  84. package/dist/commands/payment-runs.js +24 -79
  85. package/dist/commands/payment-terms.d.ts +0 -1
  86. package/dist/commands/payment-terms.js +22 -78
  87. package/dist/commands/payments.d.ts +0 -1
  88. package/dist/commands/payments.js +22 -78
  89. package/dist/commands/price-lists.d.ts +0 -1
  90. package/dist/commands/price-lists.js +22 -78
  91. package/dist/commands/processor-mappings.d.ts +0 -1
  92. package/dist/commands/processor-mappings.js +22 -78
  93. package/dist/commands/processors.d.ts +0 -1
  94. package/dist/commands/processors.js +22 -78
  95. package/dist/commands/purchase-order-types.d.ts +0 -1
  96. package/dist/commands/purchase-order-types.js +22 -78
  97. package/dist/commands/purchase-orders.d.ts +1 -1
  98. package/dist/commands/purchase-orders.js +35 -84
  99. package/dist/commands/reports.d.ts +0 -1
  100. package/dist/commands/reports.js +23 -79
  101. package/dist/commands/salary-types.d.ts +0 -1
  102. package/dist/commands/salary-types.js +22 -78
  103. package/dist/commands/sales-invoices.d.ts +0 -1
  104. package/dist/commands/sales-invoices.js +38 -89
  105. package/dist/commands/sales-orders.d.ts +0 -1
  106. package/dist/commands/sales-orders.js +26 -82
  107. package/dist/commands/saved-views.d.ts +0 -1
  108. package/dist/commands/saved-views.js +22 -78
  109. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  110. package/dist/commands/scheduled-processor-mappings.js +22 -78
  111. package/dist/commands/schedules.d.ts +0 -1
  112. package/dist/commands/schedules.js +22 -78
  113. package/dist/commands/switch.js +14 -7
  114. package/dist/commands/tag-configurations.d.ts +39 -0
  115. package/dist/commands/{documents.js → tag-configurations.js} +40 -96
  116. package/dist/commands/tags.d.ts +0 -1
  117. package/dist/commands/tags.js +22 -78
  118. package/dist/commands/tax-codes.d.ts +0 -1
  119. package/dist/commands/tax-codes.js +22 -78
  120. package/dist/commands/tax-matrix.d.ts +0 -1
  121. package/dist/commands/tax-matrix.js +22 -78
  122. package/dist/commands/tenants.d.ts +0 -1
  123. package/dist/commands/tenants.js +24 -79
  124. package/dist/commands/text-templates.d.ts +0 -1
  125. package/dist/commands/text-templates.js +22 -78
  126. package/dist/commands/tools.d.ts +0 -1
  127. package/dist/commands/tools.js +22 -78
  128. package/dist/commands/vat-declarations.d.ts +1 -1
  129. package/dist/commands/vat-declarations.js +28 -79
  130. package/dist/commands/vendor-invoices.d.ts +0 -1
  131. package/dist/commands/vendor-invoices.js +31 -86
  132. package/dist/commands/webhooks.d.ts +0 -1
  133. package/dist/commands/webhooks.js +22 -78
  134. package/dist/commands/workflows.d.ts +0 -1
  135. package/dist/commands/workflows.js +22 -78
  136. package/dist/lib/api-client.js +8 -6
  137. package/dist/lib/auth/constants.d.ts +11 -0
  138. package/dist/lib/auth/constants.js +11 -0
  139. package/dist/lib/auth/errors.d.ts +17 -0
  140. package/dist/lib/auth/errors.js +22 -0
  141. package/dist/lib/auth/flow.d.ts +27 -0
  142. package/dist/lib/auth/flow.js +163 -0
  143. package/dist/lib/auth/guards.d.ts +5 -0
  144. package/dist/lib/auth/guards.js +14 -0
  145. package/dist/lib/auth/http.d.ts +16 -0
  146. package/dist/lib/auth/http.js +49 -0
  147. package/dist/lib/auth/index.d.ts +49 -0
  148. package/dist/lib/auth/index.js +217 -0
  149. package/dist/lib/auth/keycloak-provider.d.ts +75 -0
  150. package/dist/lib/auth/keycloak-provider.js +215 -0
  151. package/dist/lib/auth/pages.d.ts +25 -0
  152. package/dist/lib/auth/pages.js +123 -0
  153. package/dist/lib/auth/pkce.d.ts +11 -0
  154. package/dist/lib/auth/pkce.js +17 -0
  155. package/dist/lib/auth/tokens.d.ts +54 -0
  156. package/dist/lib/auth/tokens.js +104 -0
  157. package/dist/lib/auth/types.d.ts +57 -0
  158. package/dist/lib/auth/types.js +1 -0
  159. package/dist/lib/auth/validated-url.d.ts +6 -0
  160. package/dist/lib/auth/validated-url.js +53 -0
  161. package/dist/lib/base-api-command.js +6 -4
  162. package/dist/lib/config.d.ts +44 -4
  163. package/dist/lib/config.js +55 -19
  164. package/oclif.manifest.json +1181 -330
  165. package/package.json +3 -2
  166. package/dist/lib/auth.d.ts +0 -35
  167. package/dist/lib/auth.js +0 -566
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
6
6
  const HAS_EXPORT = true;
@@ -32,7 +32,7 @@ export default class TaxCodes extends Command {
32
32
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
33
33
  };
34
34
  static flags = {
35
- context: Flags.string({ char: 'c', description: 'Context to use' }),
35
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
36
36
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
37
37
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
38
38
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -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
  };
@@ -104,9 +104,9 @@ export default class TaxCodes extends Command {
104
104
  }
105
105
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
106
106
  if (!context)
107
- this.error('No context configured. Run: aderis auth login --new');
108
- if (!context.tokens)
109
- this.error('Not logged in. Run: aderis auth login');
107
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
108
+ if (!hasCredentials(context))
109
+ this.error(missingCredentialsMessage(context));
110
110
  const queryParams = {};
111
111
  if (flags.page)
112
112
  queryParams.page = flags.page;
@@ -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));
@@ -320,11 +322,6 @@ export default class TaxCodes extends Command {
320
322
  output = obj[arrayKeys[0]];
321
323
  }
322
324
  }
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
325
  // Sanitize output if requested
329
326
  if (flags.sanitize) {
330
327
  output = this.sanitizeOutput(output);
@@ -349,9 +346,9 @@ export default class TaxCodes extends Command {
349
346
  async runImport(flags) {
350
347
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
351
348
  if (!context)
352
- this.error('No context configured. Run: aderis auth login --new');
353
- if (!context.tokens)
354
- this.error('Not logged in. Run: aderis auth login');
349
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
350
+ if (!hasCredentials(context))
351
+ this.error(missingCredentialsMessage(context));
355
352
  // Get data from file, --data, or --set flags
356
353
  let items = [];
357
354
  if (flags.file) {
@@ -561,9 +558,9 @@ export default class TaxCodes extends Command {
561
558
  async runExport(flags) {
562
559
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
563
560
  if (!context)
564
- this.error('No context configured. Run: aderis auth login --new');
565
- if (!context.tokens)
566
- this.error('Not logged in. Run: aderis auth login');
561
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
562
+ if (!hasCredentials(context))
563
+ this.error(missingCredentialsMessage(context));
567
564
  const listEndpoint = ENDPOINTS['list'];
568
565
  if (!listEndpoint)
569
566
  this.error('Export requires a list endpoint');
@@ -579,6 +576,8 @@ export default class TaxCodes extends Command {
579
576
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
580
577
  if (flags.include)
581
578
  baseParams.include = flags.include;
579
+ if (flags.fields)
580
+ baseParams.fields = flags.fields;
582
581
  if (flags.query) {
583
582
  try {
584
583
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -633,12 +632,7 @@ export default class TaxCodes extends Command {
633
632
  }
634
633
  }
635
634
  process.stderr.write(' done\n');
636
- // Filter fields if specified
637
635
  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
636
  // Sanitize output if requested
643
637
  if (flags.sanitize) {
644
638
  output = this.sanitizeOutput(output);
@@ -655,9 +649,9 @@ export default class TaxCodes extends Command {
655
649
  async runCamt053Import(flags) {
656
650
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
657
651
  if (!context)
658
- this.error('No context configured. Run: aderis auth login --new');
659
- if (!context.tokens)
660
- this.error('Not logged in. Run: aderis auth login');
652
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
653
+ if (!hasCredentials(context))
654
+ this.error(missingCredentialsMessage(context));
661
655
  if (!flags.file) {
662
656
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
663
657
  }
@@ -745,9 +739,9 @@ export default class TaxCodes extends Command {
745
739
  }
746
740
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
747
741
  if (!context)
748
- this.error('No context configured. Run: aderis auth login --new');
749
- if (!context.tokens)
750
- this.error('Not logged in. Run: aderis auth login');
742
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
743
+ if (!hasCredentials(context))
744
+ this.error(missingCredentialsMessage(context));
751
745
  // Get data from file, --data, or --set flags
752
746
  let items = [];
753
747
  if (flags.file) {
@@ -967,56 +961,6 @@ export default class TaxCodes extends Command {
967
961
  return `\$${op}`;
968
962
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
969
963
  }
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
964
  sanitizeOutput(data) {
1021
965
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1022
966
  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,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
6
6
  const HAS_EXPORT = true;
@@ -29,7 +29,7 @@ export default class TaxMatrix extends Command {
29
29
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
30
30
  };
31
31
  static flags = {
32
- context: Flags.string({ char: 'c', description: 'Context to use' }),
32
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
33
33
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
34
34
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
35
35
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -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
  };
@@ -101,9 +101,9 @@ export default class TaxMatrix extends Command {
101
101
  }
102
102
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
103
103
  if (!context)
104
- this.error('No context configured. Run: aderis auth login --new');
105
- if (!context.tokens)
106
- this.error('Not logged in. Run: aderis auth login');
104
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
105
+ if (!hasCredentials(context))
106
+ this.error(missingCredentialsMessage(context));
107
107
  const queryParams = {};
108
108
  if (flags.page)
109
109
  queryParams.page = flags.page;
@@ -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));
@@ -317,11 +319,6 @@ export default class TaxMatrix extends Command {
317
319
  output = obj[arrayKeys[0]];
318
320
  }
319
321
  }
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
322
  // Sanitize output if requested
326
323
  if (flags.sanitize) {
327
324
  output = this.sanitizeOutput(output);
@@ -346,9 +343,9 @@ export default class TaxMatrix extends Command {
346
343
  async runImport(flags) {
347
344
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
348
345
  if (!context)
349
- this.error('No context configured. Run: aderis auth login --new');
350
- if (!context.tokens)
351
- this.error('Not logged in. Run: aderis auth login');
346
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
347
+ if (!hasCredentials(context))
348
+ this.error(missingCredentialsMessage(context));
352
349
  // Get data from file, --data, or --set flags
353
350
  let items = [];
354
351
  if (flags.file) {
@@ -558,9 +555,9 @@ export default class TaxMatrix extends Command {
558
555
  async runExport(flags) {
559
556
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
560
557
  if (!context)
561
- this.error('No context configured. Run: aderis auth login --new');
562
- if (!context.tokens)
563
- this.error('Not logged in. Run: aderis auth login');
558
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
559
+ if (!hasCredentials(context))
560
+ this.error(missingCredentialsMessage(context));
564
561
  const listEndpoint = ENDPOINTS['list'];
565
562
  if (!listEndpoint)
566
563
  this.error('Export requires a list endpoint');
@@ -576,6 +573,8 @@ export default class TaxMatrix extends Command {
576
573
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
577
574
  if (flags.include)
578
575
  baseParams.include = flags.include;
576
+ if (flags.fields)
577
+ baseParams.fields = flags.fields;
579
578
  if (flags.query) {
580
579
  try {
581
580
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -630,12 +629,7 @@ export default class TaxMatrix extends Command {
630
629
  }
631
630
  }
632
631
  process.stderr.write(' done\n');
633
- // Filter fields if specified
634
632
  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
633
  // Sanitize output if requested
640
634
  if (flags.sanitize) {
641
635
  output = this.sanitizeOutput(output);
@@ -652,9 +646,9 @@ export default class TaxMatrix extends Command {
652
646
  async runCamt053Import(flags) {
653
647
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
654
648
  if (!context)
655
- this.error('No context configured. Run: aderis auth login --new');
656
- if (!context.tokens)
657
- this.error('Not logged in. Run: aderis auth login');
649
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
650
+ if (!hasCredentials(context))
651
+ this.error(missingCredentialsMessage(context));
658
652
  if (!flags.file) {
659
653
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
660
654
  }
@@ -742,9 +736,9 @@ export default class TaxMatrix extends Command {
742
736
  }
743
737
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
744
738
  if (!context)
745
- this.error('No context configured. Run: aderis auth login --new');
746
- if (!context.tokens)
747
- this.error('Not logged in. Run: aderis auth login');
739
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
740
+ if (!hasCredentials(context))
741
+ this.error(missingCredentialsMessage(context));
748
742
  // Get data from file, --data, or --set flags
749
743
  let items = [];
750
744
  if (flags.file) {
@@ -964,56 +958,6 @@ export default class TaxMatrix extends Command {
964
958
  return `\$${op}`;
965
959
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
966
960
  }
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
961
  sanitizeOutput(data) {
1018
962
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1019
963
  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 Tenants 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,12 +1,12 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "logo", "icon"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "logo", "icon", "update-identity-provider"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/tenants', summary: 'Get all tenants with pagination (admin only)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
12
  'create': { method: 'POST', path: '/v1/tenants', summary: 'Create a new tenant (admin only)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['supportedLocales', 'allowedSenders', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'salesOrderRecipientCc', 'salesOrderRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc'] },
@@ -15,6 +15,7 @@ const ENDPOINTS = {
15
15
  'delete': { method: 'DELETE', path: '/v1/tenants/{id}', summary: 'Delete a tenant (admin only)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
16
  'logo': { method: 'POST', path: '/v1/tenants/{id}/logo', summary: 'Upload the tenant logo to the public bucket and set logoUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
17
17
  'icon': { method: 'POST', path: '/v1/tenants/{id}/icon', summary: 'Upload the tenant icon to the public bucket and set iconUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
18
+ 'update-identity-provider': { method: 'PUT', path: '/v1/tenants/{id}/identity-provider', summary: 'Synchronize a tenant with the identity provider (admin only)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
19
  };
19
20
  export default class Tenants extends Command {
20
21
  static description = 'Manage tenants';
@@ -31,7 +32,7 @@ export default class Tenants extends Command {
31
32
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
32
33
  };
33
34
  static flags = {
34
- context: Flags.string({ char: 'c', description: 'Context to use' }),
35
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
35
36
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
36
37
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
37
38
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -45,7 +46,7 @@ export default class Tenants extends Command {
45
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.' }),
46
47
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
47
48
  query: Flags.string({ description: 'Additional query params as JSON' }),
48
- 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)' }),
49
50
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
50
51
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
51
52
  };
@@ -103,9 +104,9 @@ export default class Tenants extends Command {
103
104
  }
104
105
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
105
106
  if (!context)
106
- this.error('No context configured. Run: aderis auth login --new');
107
- if (!context.tokens)
108
- this.error('Not logged in. Run: aderis auth login');
107
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
108
+ if (!hasCredentials(context))
109
+ this.error(missingCredentialsMessage(context));
109
110
  const queryParams = {};
110
111
  if (flags.page)
111
112
  queryParams.page = flags.page;
@@ -117,6 +118,8 @@ export default class Tenants extends Command {
117
118
  queryParams.search = flags.search;
118
119
  if (flags['sort-by'])
119
120
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
121
+ if (flags.fields)
122
+ queryParams.fields = flags.fields;
120
123
  if (flags.query) {
121
124
  try {
122
125
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -319,11 +322,6 @@ export default class Tenants extends Command {
319
322
  output = obj[arrayKeys[0]];
320
323
  }
321
324
  }
322
- // Filter fields if --fields flag is provided
323
- if (flags.fields) {
324
- const fieldList = flags.fields.split(',').map(f => f.trim());
325
- output = this.filterFields(output, fieldList);
326
- }
327
325
  // Sanitize output if requested
328
326
  if (flags.sanitize) {
329
327
  output = this.sanitizeOutput(output);
@@ -348,9 +346,9 @@ export default class Tenants extends Command {
348
346
  async runImport(flags) {
349
347
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
350
348
  if (!context)
351
- this.error('No context configured. Run: aderis auth login --new');
352
- if (!context.tokens)
353
- this.error('Not logged in. Run: aderis auth login');
349
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
350
+ if (!hasCredentials(context))
351
+ this.error(missingCredentialsMessage(context));
354
352
  // Get data from file, --data, or --set flags
355
353
  let items = [];
356
354
  if (flags.file) {
@@ -560,9 +558,9 @@ export default class Tenants extends Command {
560
558
  async runExport(flags) {
561
559
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
562
560
  if (!context)
563
- this.error('No context configured. Run: aderis auth login --new');
564
- if (!context.tokens)
565
- this.error('Not logged in. Run: aderis auth login');
561
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
562
+ if (!hasCredentials(context))
563
+ this.error(missingCredentialsMessage(context));
566
564
  const listEndpoint = ENDPOINTS['list'];
567
565
  if (!listEndpoint)
568
566
  this.error('Export requires a list endpoint');
@@ -578,6 +576,8 @@ export default class Tenants extends Command {
578
576
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
579
577
  if (flags.include)
580
578
  baseParams.include = flags.include;
579
+ if (flags.fields)
580
+ baseParams.fields = flags.fields;
581
581
  if (flags.query) {
582
582
  try {
583
583
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -632,12 +632,7 @@ export default class Tenants extends Command {
632
632
  }
633
633
  }
634
634
  process.stderr.write(' done\n');
635
- // Filter fields if specified
636
635
  let output = allItems;
637
- if (flags.fields) {
638
- const fieldList = flags.fields.split(',').map(f => f.trim());
639
- output = this.filterFields(allItems, fieldList);
640
- }
641
636
  // Sanitize output if requested
642
637
  if (flags.sanitize) {
643
638
  output = this.sanitizeOutput(output);
@@ -654,9 +649,9 @@ export default class Tenants extends Command {
654
649
  async runCamt053Import(flags) {
655
650
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
656
651
  if (!context)
657
- this.error('No context configured. Run: aderis auth login --new');
658
- if (!context.tokens)
659
- this.error('Not logged in. Run: aderis auth login');
652
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
653
+ if (!hasCredentials(context))
654
+ this.error(missingCredentialsMessage(context));
660
655
  if (!flags.file) {
661
656
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
662
657
  }
@@ -744,9 +739,9 @@ export default class Tenants extends Command {
744
739
  }
745
740
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
746
741
  if (!context)
747
- this.error('No context configured. Run: aderis auth login --new');
748
- if (!context.tokens)
749
- this.error('Not logged in. Run: aderis auth login');
742
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
743
+ if (!hasCredentials(context))
744
+ this.error(missingCredentialsMessage(context));
750
745
  // Get data from file, --data, or --set flags
751
746
  let items = [];
752
747
  if (flags.file) {
@@ -966,56 +961,6 @@ export default class Tenants extends Command {
966
961
  return `\$${op}`;
967
962
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
968
963
  }
969
- filterFields(data, fields) {
970
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
971
- const pick = (obj, keys) => {
972
- // Group keys by their first segment so sibling sub-paths
973
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
974
- // and don't clobber each other on merge.
975
- const directKeys = new Set();
976
- const nestedKeys = new Map();
977
- for (const key of keys) {
978
- if (key.split('.').some(isUnsafeKey))
979
- continue;
980
- if (key.includes('.')) {
981
- const [first, ...rest] = key.split('.');
982
- if (isUnsafeKey(first))
983
- continue;
984
- const list = nestedKeys.get(first);
985
- if (list) {
986
- list.push(rest.join('.'));
987
- }
988
- else {
989
- nestedKeys.set(first, [rest.join('.')]);
990
- }
991
- }
992
- else {
993
- directKeys.add(key);
994
- }
995
- }
996
- const result = {};
997
- for (const key of directKeys) {
998
- if (key in obj)
999
- result[key] = obj[key];
1000
- }
1001
- for (const [first, restKeys] of nestedKeys) {
1002
- const val = obj[first];
1003
- if (!val || typeof val !== 'object')
1004
- continue;
1005
- if (Array.isArray(val)) {
1006
- result[first] = val.map(item => pick(item, restKeys));
1007
- }
1008
- else {
1009
- result[first] = pick(val, restKeys);
1010
- }
1011
- }
1012
- return result;
1013
- };
1014
- if (Array.isArray(data)) {
1015
- return data.map(item => pick(item, fields));
1016
- }
1017
- return pick(data, fields);
1018
- }
1019
964
  sanitizeOutput(data) {
1020
965
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1021
966
  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 TextTemplates 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;