@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;
@@ -29,7 +29,7 @@ export default class DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningReminders 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 = false;
6
6
  const HAS_EXPORT = true;
@@ -30,7 +30,7 @@ export default class DunningReminders extends Command {
30
30
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
31
31
  };
32
32
  static flags = {
33
- context: Flags.string({ char: 'c', description: 'Context to use' }),
33
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
34
34
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
35
35
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
36
36
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -44,7 +44,7 @@ export default class DunningReminders extends Command {
44
44
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
45
45
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
46
46
  query: Flags.string({ description: 'Additional query params as JSON' }),
47
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
47
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
48
48
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
49
49
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
50
50
  };
@@ -102,9 +102,9 @@ export default class DunningReminders extends Command {
102
102
  }
103
103
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
104
104
  if (!context)
105
- this.error('No context configured. Run: aderis auth login --new');
106
- if (!context.tokens)
107
- this.error('Not logged in. Run: aderis auth login');
105
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
106
+ if (!hasCredentials(context))
107
+ this.error(missingCredentialsMessage(context));
108
108
  const queryParams = {};
109
109
  if (flags.page)
110
110
  queryParams.page = flags.page;
@@ -116,6 +116,8 @@ export default class DunningReminders extends Command {
116
116
  queryParams.search = flags.search;
117
117
  if (flags['sort-by'])
118
118
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
119
+ if (flags.fields)
120
+ queryParams.fields = flags.fields;
119
121
  if (flags.query) {
120
122
  try {
121
123
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -318,11 +320,6 @@ export default class DunningReminders extends Command {
318
320
  output = obj[arrayKeys[0]];
319
321
  }
320
322
  }
321
- // Filter fields if --fields flag is provided
322
- if (flags.fields) {
323
- const fieldList = flags.fields.split(',').map(f => f.trim());
324
- output = this.filterFields(output, fieldList);
325
- }
326
323
  // Sanitize output if requested
327
324
  if (flags.sanitize) {
328
325
  output = this.sanitizeOutput(output);
@@ -347,9 +344,9 @@ export default class DunningReminders extends Command {
347
344
  async runImport(flags) {
348
345
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
349
346
  if (!context)
350
- this.error('No context configured. Run: aderis auth login --new');
351
- if (!context.tokens)
352
- this.error('Not logged in. Run: aderis auth login');
347
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
348
+ if (!hasCredentials(context))
349
+ this.error(missingCredentialsMessage(context));
353
350
  // Get data from file, --data, or --set flags
354
351
  let items = [];
355
352
  if (flags.file) {
@@ -559,9 +556,9 @@ export default class DunningReminders extends Command {
559
556
  async runExport(flags) {
560
557
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
561
558
  if (!context)
562
- this.error('No context configured. Run: aderis auth login --new');
563
- if (!context.tokens)
564
- this.error('Not logged in. Run: aderis auth login');
559
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
560
+ if (!hasCredentials(context))
561
+ this.error(missingCredentialsMessage(context));
565
562
  const listEndpoint = ENDPOINTS['list'];
566
563
  if (!listEndpoint)
567
564
  this.error('Export requires a list endpoint');
@@ -577,6 +574,8 @@ export default class DunningReminders extends Command {
577
574
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
578
575
  if (flags.include)
579
576
  baseParams.include = flags.include;
577
+ if (flags.fields)
578
+ baseParams.fields = flags.fields;
580
579
  if (flags.query) {
581
580
  try {
582
581
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -631,12 +630,7 @@ export default class DunningReminders extends Command {
631
630
  }
632
631
  }
633
632
  process.stderr.write(' done\n');
634
- // Filter fields if specified
635
633
  let output = allItems;
636
- if (flags.fields) {
637
- const fieldList = flags.fields.split(',').map(f => f.trim());
638
- output = this.filterFields(allItems, fieldList);
639
- }
640
634
  // Sanitize output if requested
641
635
  if (flags.sanitize) {
642
636
  output = this.sanitizeOutput(output);
@@ -653,9 +647,9 @@ export default class DunningReminders extends Command {
653
647
  async runCamt053Import(flags) {
654
648
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
655
649
  if (!context)
656
- this.error('No context configured. Run: aderis auth login --new');
657
- if (!context.tokens)
658
- this.error('Not logged in. Run: aderis auth login');
650
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
651
+ if (!hasCredentials(context))
652
+ this.error(missingCredentialsMessage(context));
659
653
  if (!flags.file) {
660
654
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
661
655
  }
@@ -743,9 +737,9 @@ export default class DunningReminders extends Command {
743
737
  }
744
738
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
745
739
  if (!context)
746
- this.error('No context configured. Run: aderis auth login --new');
747
- if (!context.tokens)
748
- this.error('Not logged in. Run: aderis auth login');
740
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
741
+ if (!hasCredentials(context))
742
+ this.error(missingCredentialsMessage(context));
749
743
  // Get data from file, --data, or --set flags
750
744
  let items = [];
751
745
  if (flags.file) {
@@ -965,56 +959,6 @@ export default class DunningReminders extends Command {
965
959
  return `\$${op}`;
966
960
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
967
961
  }
968
- filterFields(data, fields) {
969
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
970
- const pick = (obj, keys) => {
971
- // Group keys by their first segment so sibling sub-paths
972
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
973
- // and don't clobber each other on merge.
974
- const directKeys = new Set();
975
- const nestedKeys = new Map();
976
- for (const key of keys) {
977
- if (key.split('.').some(isUnsafeKey))
978
- continue;
979
- if (key.includes('.')) {
980
- const [first, ...rest] = key.split('.');
981
- if (isUnsafeKey(first))
982
- continue;
983
- const list = nestedKeys.get(first);
984
- if (list) {
985
- list.push(rest.join('.'));
986
- }
987
- else {
988
- nestedKeys.set(first, [rest.join('.')]);
989
- }
990
- }
991
- else {
992
- directKeys.add(key);
993
- }
994
- }
995
- const result = {};
996
- for (const key of directKeys) {
997
- if (key in obj)
998
- result[key] = obj[key];
999
- }
1000
- for (const [first, restKeys] of nestedKeys) {
1001
- const val = obj[first];
1002
- if (!val || typeof val !== 'object')
1003
- continue;
1004
- if (Array.isArray(val)) {
1005
- result[first] = val.map(item => pick(item, restKeys));
1006
- }
1007
- else {
1008
- result[first] = pick(val, restKeys);
1009
- }
1010
- }
1011
- return result;
1012
- };
1013
- if (Array.isArray(data)) {
1014
- return data.map(item => pick(item, fields));
1015
- }
1016
- return pick(data, fields);
1017
- }
1018
962
  sanitizeOutput(data) {
1019
963
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1020
964
  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 DunningRuns 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 = false;
6
6
  const HAS_EXPORT = false;
@@ -18,7 +18,7 @@ export default class DunningRuns extends Command {
18
18
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
19
19
  };
20
20
  static flags = {
21
- context: Flags.string({ char: 'c', description: 'Context to use' }),
21
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
22
22
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
23
23
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
24
24
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -32,7 +32,7 @@ export default class DunningRuns extends Command {
32
32
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
33
33
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
34
34
  query: Flags.string({ description: 'Additional query params as JSON' }),
35
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
35
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
36
36
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
37
37
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
38
38
  };
@@ -90,9 +90,9 @@ export default class DunningRuns extends Command {
90
90
  }
91
91
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
92
92
  if (!context)
93
- this.error('No context configured. Run: aderis auth login --new');
94
- if (!context.tokens)
95
- this.error('Not logged in. Run: aderis auth login');
93
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
94
+ if (!hasCredentials(context))
95
+ this.error(missingCredentialsMessage(context));
96
96
  const queryParams = {};
97
97
  if (flags.page)
98
98
  queryParams.page = flags.page;
@@ -104,6 +104,8 @@ export default class DunningRuns extends Command {
104
104
  queryParams.search = flags.search;
105
105
  if (flags['sort-by'])
106
106
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
107
+ if (flags.fields)
108
+ queryParams.fields = flags.fields;
107
109
  if (flags.query) {
108
110
  try {
109
111
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -306,11 +308,6 @@ export default class DunningRuns extends Command {
306
308
  output = obj[arrayKeys[0]];
307
309
  }
308
310
  }
309
- // Filter fields if --fields flag is provided
310
- if (flags.fields) {
311
- const fieldList = flags.fields.split(',').map(f => f.trim());
312
- output = this.filterFields(output, fieldList);
313
- }
314
311
  // Sanitize output if requested
315
312
  if (flags.sanitize) {
316
313
  output = this.sanitizeOutput(output);
@@ -335,9 +332,9 @@ export default class DunningRuns extends Command {
335
332
  async runImport(flags) {
336
333
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
337
334
  if (!context)
338
- this.error('No context configured. Run: aderis auth login --new');
339
- if (!context.tokens)
340
- this.error('Not logged in. Run: aderis auth login');
335
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
336
+ if (!hasCredentials(context))
337
+ this.error(missingCredentialsMessage(context));
341
338
  // Get data from file, --data, or --set flags
342
339
  let items = [];
343
340
  if (flags.file) {
@@ -547,9 +544,9 @@ export default class DunningRuns extends Command {
547
544
  async runExport(flags) {
548
545
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
549
546
  if (!context)
550
- this.error('No context configured. Run: aderis auth login --new');
551
- if (!context.tokens)
552
- this.error('Not logged in. Run: aderis auth login');
547
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
548
+ if (!hasCredentials(context))
549
+ this.error(missingCredentialsMessage(context));
553
550
  const listEndpoint = ENDPOINTS['list'];
554
551
  if (!listEndpoint)
555
552
  this.error('Export requires a list endpoint');
@@ -565,6 +562,8 @@ export default class DunningRuns extends Command {
565
562
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
566
563
  if (flags.include)
567
564
  baseParams.include = flags.include;
565
+ if (flags.fields)
566
+ baseParams.fields = flags.fields;
568
567
  if (flags.query) {
569
568
  try {
570
569
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -619,12 +618,7 @@ export default class DunningRuns extends Command {
619
618
  }
620
619
  }
621
620
  process.stderr.write(' done\n');
622
- // Filter fields if specified
623
621
  let output = allItems;
624
- if (flags.fields) {
625
- const fieldList = flags.fields.split(',').map(f => f.trim());
626
- output = this.filterFields(allItems, fieldList);
627
- }
628
622
  // Sanitize output if requested
629
623
  if (flags.sanitize) {
630
624
  output = this.sanitizeOutput(output);
@@ -641,9 +635,9 @@ export default class DunningRuns extends Command {
641
635
  async runCamt053Import(flags) {
642
636
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
643
637
  if (!context)
644
- this.error('No context configured. Run: aderis auth login --new');
645
- if (!context.tokens)
646
- this.error('Not logged in. Run: aderis auth login');
638
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
639
+ if (!hasCredentials(context))
640
+ this.error(missingCredentialsMessage(context));
647
641
  if (!flags.file) {
648
642
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
649
643
  }
@@ -731,9 +725,9 @@ export default class DunningRuns extends Command {
731
725
  }
732
726
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
733
727
  if (!context)
734
- this.error('No context configured. Run: aderis auth login --new');
735
- if (!context.tokens)
736
- this.error('Not logged in. Run: aderis auth login');
728
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
729
+ if (!hasCredentials(context))
730
+ this.error(missingCredentialsMessage(context));
737
731
  // Get data from file, --data, or --set flags
738
732
  let items = [];
739
733
  if (flags.file) {
@@ -953,56 +947,6 @@ export default class DunningRuns extends Command {
953
947
  return `\$${op}`;
954
948
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
955
949
  }
956
- filterFields(data, fields) {
957
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
958
- const pick = (obj, keys) => {
959
- // Group keys by their first segment so sibling sub-paths
960
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
961
- // and don't clobber each other on merge.
962
- const directKeys = new Set();
963
- const nestedKeys = new Map();
964
- for (const key of keys) {
965
- if (key.split('.').some(isUnsafeKey))
966
- continue;
967
- if (key.includes('.')) {
968
- const [first, ...rest] = key.split('.');
969
- if (isUnsafeKey(first))
970
- continue;
971
- const list = nestedKeys.get(first);
972
- if (list) {
973
- list.push(rest.join('.'));
974
- }
975
- else {
976
- nestedKeys.set(first, [rest.join('.')]);
977
- }
978
- }
979
- else {
980
- directKeys.add(key);
981
- }
982
- }
983
- const result = {};
984
- for (const key of directKeys) {
985
- if (key in obj)
986
- result[key] = obj[key];
987
- }
988
- for (const [first, restKeys] of nestedKeys) {
989
- const val = obj[first];
990
- if (!val || typeof val !== 'object')
991
- continue;
992
- if (Array.isArray(val)) {
993
- result[first] = val.map(item => pick(item, restKeys));
994
- }
995
- else {
996
- result[first] = pick(val, restKeys);
997
- }
998
- }
999
- return result;
1000
- };
1001
- if (Array.isArray(data)) {
1002
- return data.map(item => pick(item, fields));
1003
- }
1004
- return pick(data, fields);
1005
- }
1006
950
  sanitizeOutput(data) {
1007
951
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1008
952
  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 EmailTransactions 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;