@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 = false;
6
6
  const HAS_EXPORT = false;
@@ -23,7 +23,7 @@ export default class SavedViews extends Command {
23
23
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
24
24
  };
25
25
  static flags = {
26
- context: Flags.string({ char: 'c', description: 'Context to use' }),
26
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
27
27
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
28
28
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
29
29
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -37,7 +37,7 @@ export default class SavedViews extends Command {
37
37
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
38
38
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
39
39
  query: Flags.string({ description: 'Additional query params as JSON' }),
40
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
40
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
41
41
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
42
42
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
43
43
  };
@@ -95,9 +95,9 @@ export default class SavedViews extends Command {
95
95
  }
96
96
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
97
97
  if (!context)
98
- this.error('No context configured. Run: aderis auth login --new');
99
- if (!context.tokens)
100
- this.error('Not logged in. Run: aderis auth login');
98
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
99
+ if (!hasCredentials(context))
100
+ this.error(missingCredentialsMessage(context));
101
101
  const queryParams = {};
102
102
  if (flags.page)
103
103
  queryParams.page = flags.page;
@@ -109,6 +109,8 @@ export default class SavedViews extends Command {
109
109
  queryParams.search = flags.search;
110
110
  if (flags['sort-by'])
111
111
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
112
+ if (flags.fields)
113
+ queryParams.fields = flags.fields;
112
114
  if (flags.query) {
113
115
  try {
114
116
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -311,11 +313,6 @@ export default class SavedViews extends Command {
311
313
  output = obj[arrayKeys[0]];
312
314
  }
313
315
  }
314
- // Filter fields if --fields flag is provided
315
- if (flags.fields) {
316
- const fieldList = flags.fields.split(',').map(f => f.trim());
317
- output = this.filterFields(output, fieldList);
318
- }
319
316
  // Sanitize output if requested
320
317
  if (flags.sanitize) {
321
318
  output = this.sanitizeOutput(output);
@@ -340,9 +337,9 @@ export default class SavedViews extends Command {
340
337
  async runImport(flags) {
341
338
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
342
339
  if (!context)
343
- this.error('No context configured. Run: aderis auth login --new');
344
- if (!context.tokens)
345
- this.error('Not logged in. Run: aderis auth login');
340
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
341
+ if (!hasCredentials(context))
342
+ this.error(missingCredentialsMessage(context));
346
343
  // Get data from file, --data, or --set flags
347
344
  let items = [];
348
345
  if (flags.file) {
@@ -552,9 +549,9 @@ export default class SavedViews extends Command {
552
549
  async runExport(flags) {
553
550
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
554
551
  if (!context)
555
- this.error('No context configured. Run: aderis auth login --new');
556
- if (!context.tokens)
557
- this.error('Not logged in. Run: aderis auth login');
552
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
553
+ if (!hasCredentials(context))
554
+ this.error(missingCredentialsMessage(context));
558
555
  const listEndpoint = ENDPOINTS['list'];
559
556
  if (!listEndpoint)
560
557
  this.error('Export requires a list endpoint');
@@ -570,6 +567,8 @@ export default class SavedViews extends Command {
570
567
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
571
568
  if (flags.include)
572
569
  baseParams.include = flags.include;
570
+ if (flags.fields)
571
+ baseParams.fields = flags.fields;
573
572
  if (flags.query) {
574
573
  try {
575
574
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -624,12 +623,7 @@ export default class SavedViews extends Command {
624
623
  }
625
624
  }
626
625
  process.stderr.write(' done\n');
627
- // Filter fields if specified
628
626
  let output = allItems;
629
- if (flags.fields) {
630
- const fieldList = flags.fields.split(',').map(f => f.trim());
631
- output = this.filterFields(allItems, fieldList);
632
- }
633
627
  // Sanitize output if requested
634
628
  if (flags.sanitize) {
635
629
  output = this.sanitizeOutput(output);
@@ -646,9 +640,9 @@ export default class SavedViews extends Command {
646
640
  async runCamt053Import(flags) {
647
641
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
648
642
  if (!context)
649
- this.error('No context configured. Run: aderis auth login --new');
650
- if (!context.tokens)
651
- this.error('Not logged in. Run: aderis auth login');
643
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
644
+ if (!hasCredentials(context))
645
+ this.error(missingCredentialsMessage(context));
652
646
  if (!flags.file) {
653
647
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
654
648
  }
@@ -736,9 +730,9 @@ export default class SavedViews extends Command {
736
730
  }
737
731
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
738
732
  if (!context)
739
- this.error('No context configured. Run: aderis auth login --new');
740
- if (!context.tokens)
741
- this.error('Not logged in. Run: aderis auth login');
733
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
734
+ if (!hasCredentials(context))
735
+ this.error(missingCredentialsMessage(context));
742
736
  // Get data from file, --data, or --set flags
743
737
  let items = [];
744
738
  if (flags.file) {
@@ -958,56 +952,6 @@ export default class SavedViews extends Command {
958
952
  return `\$${op}`;
959
953
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
960
954
  }
961
- filterFields(data, fields) {
962
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
963
- const pick = (obj, keys) => {
964
- // Group keys by their first segment so sibling sub-paths
965
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
966
- // and don't clobber each other on merge.
967
- const directKeys = new Set();
968
- const nestedKeys = new Map();
969
- for (const key of keys) {
970
- if (key.split('.').some(isUnsafeKey))
971
- continue;
972
- if (key.includes('.')) {
973
- const [first, ...rest] = key.split('.');
974
- if (isUnsafeKey(first))
975
- continue;
976
- const list = nestedKeys.get(first);
977
- if (list) {
978
- list.push(rest.join('.'));
979
- }
980
- else {
981
- nestedKeys.set(first, [rest.join('.')]);
982
- }
983
- }
984
- else {
985
- directKeys.add(key);
986
- }
987
- }
988
- const result = {};
989
- for (const key of directKeys) {
990
- if (key in obj)
991
- result[key] = obj[key];
992
- }
993
- for (const [first, restKeys] of nestedKeys) {
994
- const val = obj[first];
995
- if (!val || typeof val !== 'object')
996
- continue;
997
- if (Array.isArray(val)) {
998
- result[first] = val.map(item => pick(item, restKeys));
999
- }
1000
- else {
1001
- result[first] = pick(val, restKeys);
1002
- }
1003
- }
1004
- return result;
1005
- };
1006
- if (Array.isArray(data)) {
1007
- return data.map(item => pick(item, fields));
1008
- }
1009
- return pick(data, fields);
1010
- }
1011
955
  sanitizeOutput(data) {
1012
956
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1013
957
  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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 Schedules 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;
@@ -33,7 +33,7 @@ export default class Schedules extends Command {
33
33
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
34
34
  };
35
35
  static flags = {
36
- context: Flags.string({ char: 'c', description: 'Context to use' }),
36
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
37
37
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
38
38
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
39
39
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -47,7 +47,7 @@ export default class Schedules extends Command {
47
47
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
48
48
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
49
49
  query: Flags.string({ description: 'Additional query params as JSON' }),
50
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
50
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
51
51
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
52
52
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
53
53
  };
@@ -105,9 +105,9 @@ export default class Schedules extends Command {
105
105
  }
106
106
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
107
107
  if (!context)
108
- this.error('No context configured. Run: aderis auth login --new');
109
- if (!context.tokens)
110
- this.error('Not logged in. Run: aderis auth login');
108
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
109
+ if (!hasCredentials(context))
110
+ this.error(missingCredentialsMessage(context));
111
111
  const queryParams = {};
112
112
  if (flags.page)
113
113
  queryParams.page = flags.page;
@@ -119,6 +119,8 @@ export default class Schedules extends Command {
119
119
  queryParams.search = flags.search;
120
120
  if (flags['sort-by'])
121
121
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
122
+ if (flags.fields)
123
+ queryParams.fields = flags.fields;
122
124
  if (flags.query) {
123
125
  try {
124
126
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -321,11 +323,6 @@ export default class Schedules extends Command {
321
323
  output = obj[arrayKeys[0]];
322
324
  }
323
325
  }
324
- // Filter fields if --fields flag is provided
325
- if (flags.fields) {
326
- const fieldList = flags.fields.split(',').map(f => f.trim());
327
- output = this.filterFields(output, fieldList);
328
- }
329
326
  // Sanitize output if requested
330
327
  if (flags.sanitize) {
331
328
  output = this.sanitizeOutput(output);
@@ -350,9 +347,9 @@ export default class Schedules extends Command {
350
347
  async runImport(flags) {
351
348
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
352
349
  if (!context)
353
- this.error('No context configured. Run: aderis auth login --new');
354
- if (!context.tokens)
355
- this.error('Not logged in. Run: aderis auth login');
350
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
351
+ if (!hasCredentials(context))
352
+ this.error(missingCredentialsMessage(context));
356
353
  // Get data from file, --data, or --set flags
357
354
  let items = [];
358
355
  if (flags.file) {
@@ -562,9 +559,9 @@ export default class Schedules extends Command {
562
559
  async runExport(flags) {
563
560
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
564
561
  if (!context)
565
- this.error('No context configured. Run: aderis auth login --new');
566
- if (!context.tokens)
567
- this.error('Not logged in. Run: aderis auth login');
562
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
563
+ if (!hasCredentials(context))
564
+ this.error(missingCredentialsMessage(context));
568
565
  const listEndpoint = ENDPOINTS['list'];
569
566
  if (!listEndpoint)
570
567
  this.error('Export requires a list endpoint');
@@ -580,6 +577,8 @@ export default class Schedules extends Command {
580
577
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
581
578
  if (flags.include)
582
579
  baseParams.include = flags.include;
580
+ if (flags.fields)
581
+ baseParams.fields = flags.fields;
583
582
  if (flags.query) {
584
583
  try {
585
584
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -634,12 +633,7 @@ export default class Schedules extends Command {
634
633
  }
635
634
  }
636
635
  process.stderr.write(' done\n');
637
- // Filter fields if specified
638
636
  let output = allItems;
639
- if (flags.fields) {
640
- const fieldList = flags.fields.split(',').map(f => f.trim());
641
- output = this.filterFields(allItems, fieldList);
642
- }
643
637
  // Sanitize output if requested
644
638
  if (flags.sanitize) {
645
639
  output = this.sanitizeOutput(output);
@@ -656,9 +650,9 @@ export default class Schedules extends Command {
656
650
  async runCamt053Import(flags) {
657
651
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
658
652
  if (!context)
659
- this.error('No context configured. Run: aderis auth login --new');
660
- if (!context.tokens)
661
- this.error('Not logged in. Run: aderis auth login');
653
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
654
+ if (!hasCredentials(context))
655
+ this.error(missingCredentialsMessage(context));
662
656
  if (!flags.file) {
663
657
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
664
658
  }
@@ -746,9 +740,9 @@ export default class Schedules extends Command {
746
740
  }
747
741
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
748
742
  if (!context)
749
- this.error('No context configured. Run: aderis auth login --new');
750
- if (!context.tokens)
751
- this.error('Not logged in. Run: aderis auth login');
743
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
744
+ if (!hasCredentials(context))
745
+ this.error(missingCredentialsMessage(context));
752
746
  // Get data from file, --data, or --set flags
753
747
  let items = [];
754
748
  if (flags.file) {
@@ -968,56 +962,6 @@ export default class Schedules extends Command {
968
962
  return `\$${op}`;
969
963
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
970
964
  }
971
- filterFields(data, fields) {
972
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
973
- const pick = (obj, keys) => {
974
- // Group keys by their first segment so sibling sub-paths
975
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
976
- // and don't clobber each other on merge.
977
- const directKeys = new Set();
978
- const nestedKeys = new Map();
979
- for (const key of keys) {
980
- if (key.split('.').some(isUnsafeKey))
981
- continue;
982
- if (key.includes('.')) {
983
- const [first, ...rest] = key.split('.');
984
- if (isUnsafeKey(first))
985
- continue;
986
- const list = nestedKeys.get(first);
987
- if (list) {
988
- list.push(rest.join('.'));
989
- }
990
- else {
991
- nestedKeys.set(first, [rest.join('.')]);
992
- }
993
- }
994
- else {
995
- directKeys.add(key);
996
- }
997
- }
998
- const result = {};
999
- for (const key of directKeys) {
1000
- if (key in obj)
1001
- result[key] = obj[key];
1002
- }
1003
- for (const [first, restKeys] of nestedKeys) {
1004
- const val = obj[first];
1005
- if (!val || typeof val !== 'object')
1006
- continue;
1007
- if (Array.isArray(val)) {
1008
- result[first] = val.map(item => pick(item, restKeys));
1009
- }
1010
- else {
1011
- result[first] = pick(val, restKeys);
1012
- }
1013
- }
1014
- return result;
1015
- };
1016
- if (Array.isArray(data)) {
1017
- return data.map(item => pick(item, fields));
1018
- }
1019
- return pick(data, fields);
1020
- }
1021
965
  sanitizeOutput(data) {
1022
966
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1023
967
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1,7 +1,7 @@
1
1
  import { confirm, select } from '@inquirer/prompts';
2
2
  import { Command } from '@oclif/core';
3
- import { loginAndSwitchTenant } from '../lib/auth.js';
4
- import { getContext, getCurrentContext, listContexts, setCurrentContext } from '../lib/config.js';
3
+ import { loginAndSwitchTenant, tenantLabel } from '../lib/auth/index.js';
4
+ import { getAuthType, getContext, getCurrentContext, hasCredentials, listContexts, setCurrentContext, } from '../lib/config.js';
5
5
  export default class Switch extends Command {
6
6
  static description = 'Switch context and tenant interactively';
7
7
  static examples = ['<%= config.bin %> switch'];
@@ -23,9 +23,10 @@ export default class Switch extends Command {
23
23
  const selectedContext = await select({
24
24
  choices: sortedContexts.map((ctx) => {
25
25
  const isCurrent = current?.name === ctx.name;
26
- const auth = ctx.tokens ? '✓' : '✗';
26
+ const auth = hasCredentials(ctx) ? '✓' : '✗';
27
+ const kind = getAuthType(ctx) === 'api_key' ? ' api-key' : '';
27
28
  return {
28
- name: `${ctx.name} [${ctx.environment}] auth:${auth}${isCurrent ? ' (current)' : ''}`,
29
+ name: `${ctx.name} [${ctx.environment}] auth:${auth}${kind}${isCurrent ? ' (current)' : ''}`,
29
30
  value: ctx.name,
30
31
  };
31
32
  }),
@@ -35,6 +36,12 @@ export default class Switch extends Command {
35
36
  setCurrentContext(selectedContext);
36
37
  this.log(`Switched to context: ${selectedContext}`);
37
38
  }
39
+ // An API key is issued for one tenant, so there is no tenant step to run.
40
+ const selected = getContext(selectedContext);
41
+ if (selected && getAuthType(selected) === 'api_key') {
42
+ this.log('This context authenticates with an API key, which is scoped to a single tenant.');
43
+ return;
44
+ }
38
45
  // Step 2: Select tenant within chosen context.
39
46
  // If the session is expired, prompt the user to re-login and then retry.
40
47
  const { default: AuthTenant } = await import('./auth/tenant.js');
@@ -55,8 +62,8 @@ export default class Switch extends Command {
55
62
  if (!shouldLogin)
56
63
  return;
57
64
  // If we have a cached tenant list from a previous session, let the user pick
58
- // tenant FIRST. Then run the combined login + tenant-switch flow which uses
59
- // a single browser tab (chained PKCE rounds with a meta-refresh redirect).
65
+ // tenant FIRST. Then run the combined login + tenant-switch flow, which
66
+ // uses a single browser tab.
60
67
  const ctx = getContext(selectedContext);
61
68
  const cached = ctx?.cachedTenants ?? [];
62
69
  if (cached.length > 1) {
@@ -69,7 +76,7 @@ export default class Switch extends Command {
69
76
  });
70
77
  const selectedTenant = await select({
71
78
  choices: sorted.map((t) => ({
72
- name: t.tenantId === ctx?.tenant_id ? `${t.name} (previous)` : t.name,
79
+ name: t.tenantId === ctx?.tenant_id ? `${tenantLabel(t)} (previous)` : tenantLabel(t),
73
80
  value: t.tenantId,
74
81
  })),
75
82
  message: 'Select a tenant:',