@aderis-erp/cli 1.0.15 → 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 (163) 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 +5 -61
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +5 -61
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +9 -63
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +5 -61
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +5 -61
  12. package/dist/commands/analytics.d.ts +0 -1
  13. package/dist/commands/analytics.js +5 -61
  14. package/dist/commands/api-keys.d.ts +39 -0
  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 +5 -61
  18. package/dist/commands/auth/login.d.ts +9 -0
  19. package/dist/commands/auth/login.js +73 -20
  20. package/dist/commands/auth/logout.d.ts +4 -0
  21. package/dist/commands/auth/logout.js +4 -0
  22. package/dist/commands/auth/status.js +26 -17
  23. package/dist/commands/auth/tenant.js +3 -6
  24. package/dist/commands/auth.d.ts +0 -1
  25. package/dist/commands/auth.js +5 -34
  26. package/dist/commands/bank-accounts.d.ts +0 -1
  27. package/dist/commands/bank-accounts.js +5 -61
  28. package/dist/commands/bank-integrations.d.ts +0 -1
  29. package/dist/commands/bank-integrations.js +5 -61
  30. package/dist/commands/business-partners.d.ts +1 -1
  31. package/dist/commands/business-partners.js +14 -63
  32. package/dist/commands/chat.d.ts +0 -1
  33. package/dist/commands/chat.js +5 -61
  34. package/dist/commands/config/add.d.ts +3 -0
  35. package/dist/commands/config/add.js +34 -16
  36. package/dist/commands/config/current.js +3 -0
  37. package/dist/commands/config/list.js +3 -0
  38. package/dist/commands/credit-card-accounts.d.ts +0 -1
  39. package/dist/commands/credit-card-accounts.js +5 -61
  40. package/dist/commands/delivery-notes.d.ts +0 -1
  41. package/dist/commands/delivery-notes.js +7 -63
  42. package/dist/commands/dunning-configs.d.ts +0 -1
  43. package/dist/commands/dunning-configs.js +5 -61
  44. package/dist/commands/dunning-reminders.d.ts +0 -1
  45. package/dist/commands/dunning-reminders.js +5 -61
  46. package/dist/commands/dunning-runs.d.ts +0 -1
  47. package/dist/commands/dunning-runs.js +5 -61
  48. package/dist/commands/email-transactions.d.ts +0 -1
  49. package/dist/commands/email-transactions.js +5 -61
  50. package/dist/commands/employees.d.ts +0 -1
  51. package/dist/commands/employees.js +5 -61
  52. package/dist/commands/entity-configurations.d.ts +0 -1
  53. package/dist/commands/entity-configurations.js +5 -61
  54. package/dist/commands/event-processor-mappings.d.ts +0 -1
  55. package/dist/commands/event-processor-mappings.js +5 -61
  56. package/dist/commands/files.d.ts +0 -1
  57. package/dist/commands/files.js +5 -61
  58. package/dist/commands/health.d.ts +0 -1
  59. package/dist/commands/health.js +5 -61
  60. package/dist/commands/inbox.d.ts +0 -1
  61. package/dist/commands/inbox.js +5 -61
  62. package/dist/commands/integrations.d.ts +0 -1
  63. package/dist/commands/integrations.js +5 -61
  64. package/dist/commands/item-categories.d.ts +0 -1
  65. package/dist/commands/item-categories.js +5 -61
  66. package/dist/commands/items.d.ts +0 -1
  67. package/dist/commands/items.js +5 -61
  68. package/dist/commands/journal-lines.d.ts +0 -1
  69. package/dist/commands/journal-lines.js +5 -61
  70. package/dist/commands/journals.d.ts +1 -1
  71. package/dist/commands/journals.js +9 -62
  72. package/dist/commands/ledgers.d.ts +0 -1
  73. package/dist/commands/ledgers.js +5 -61
  74. package/dist/commands/nango-integrations.d.ts +39 -0
  75. package/dist/commands/nango-integrations.js +1115 -0
  76. package/dist/commands/office-locations.d.ts +0 -1
  77. package/dist/commands/office-locations.js +5 -61
  78. package/dist/commands/payment-methods.d.ts +0 -1
  79. package/dist/commands/payment-methods.js +5 -61
  80. package/dist/commands/payment-runs.d.ts +0 -1
  81. package/dist/commands/payment-runs.js +5 -61
  82. package/dist/commands/payment-terms.d.ts +0 -1
  83. package/dist/commands/payment-terms.js +5 -61
  84. package/dist/commands/payments.d.ts +0 -1
  85. package/dist/commands/payments.js +5 -61
  86. package/dist/commands/price-lists.d.ts +0 -1
  87. package/dist/commands/price-lists.js +5 -61
  88. package/dist/commands/processor-mappings.d.ts +0 -1
  89. package/dist/commands/processor-mappings.js +5 -61
  90. package/dist/commands/processors.d.ts +0 -1
  91. package/dist/commands/processors.js +5 -61
  92. package/dist/commands/purchase-order-types.d.ts +0 -1
  93. package/dist/commands/purchase-order-types.js +5 -61
  94. package/dist/commands/purchase-orders.d.ts +0 -1
  95. package/dist/commands/purchase-orders.js +7 -63
  96. package/dist/commands/reports.d.ts +0 -1
  97. package/dist/commands/reports.js +6 -62
  98. package/dist/commands/salary-types.d.ts +0 -1
  99. package/dist/commands/salary-types.js +5 -61
  100. package/dist/commands/sales-invoices.d.ts +0 -1
  101. package/dist/commands/sales-invoices.js +12 -65
  102. package/dist/commands/sales-orders.d.ts +0 -1
  103. package/dist/commands/sales-orders.js +7 -63
  104. package/dist/commands/saved-views.d.ts +0 -1
  105. package/dist/commands/saved-views.js +5 -61
  106. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  107. package/dist/commands/scheduled-processor-mappings.js +5 -61
  108. package/dist/commands/schedules.d.ts +0 -1
  109. package/dist/commands/schedules.js +5 -61
  110. package/dist/commands/switch.js +4 -4
  111. package/dist/commands/tag-configurations.d.ts +0 -1
  112. package/dist/commands/tag-configurations.js +9 -64
  113. package/dist/commands/tags.d.ts +0 -1
  114. package/dist/commands/tags.js +5 -61
  115. package/dist/commands/tax-codes.d.ts +0 -1
  116. package/dist/commands/tax-codes.js +5 -61
  117. package/dist/commands/tax-matrix.d.ts +0 -1
  118. package/dist/commands/tax-matrix.js +5 -61
  119. package/dist/commands/tenants.d.ts +0 -1
  120. package/dist/commands/tenants.js +7 -62
  121. package/dist/commands/text-templates.d.ts +0 -1
  122. package/dist/commands/text-templates.js +5 -61
  123. package/dist/commands/tools.d.ts +0 -1
  124. package/dist/commands/tools.js +5 -61
  125. package/dist/commands/vat-declarations.d.ts +1 -1
  126. package/dist/commands/vat-declarations.js +11 -62
  127. package/dist/commands/vendor-invoices.d.ts +0 -1
  128. package/dist/commands/vendor-invoices.js +10 -65
  129. package/dist/commands/webhooks.d.ts +0 -1
  130. package/dist/commands/webhooks.js +5 -61
  131. package/dist/commands/workflows.d.ts +0 -1
  132. package/dist/commands/workflows.js +5 -61
  133. package/dist/lib/api-client.js +3 -6
  134. package/dist/lib/auth/constants.d.ts +11 -0
  135. package/dist/lib/auth/constants.js +11 -0
  136. package/dist/lib/auth/errors.d.ts +17 -0
  137. package/dist/lib/auth/errors.js +22 -0
  138. package/dist/lib/auth/flow.d.ts +27 -0
  139. package/dist/lib/auth/flow.js +163 -0
  140. package/dist/lib/auth/guards.d.ts +5 -0
  141. package/dist/lib/auth/guards.js +14 -0
  142. package/dist/lib/auth/http.d.ts +16 -0
  143. package/dist/lib/auth/http.js +49 -0
  144. package/dist/lib/auth/index.d.ts +49 -0
  145. package/dist/lib/auth/index.js +217 -0
  146. package/dist/lib/auth/keycloak-provider.d.ts +75 -0
  147. package/dist/lib/auth/keycloak-provider.js +215 -0
  148. package/dist/lib/auth/pages.d.ts +25 -0
  149. package/dist/lib/auth/pages.js +123 -0
  150. package/dist/lib/auth/pkce.d.ts +11 -0
  151. package/dist/lib/auth/pkce.js +17 -0
  152. package/dist/lib/auth/tokens.d.ts +54 -0
  153. package/dist/lib/auth/tokens.js +104 -0
  154. package/dist/lib/auth/types.d.ts +57 -0
  155. package/dist/lib/auth/types.js +1 -0
  156. package/dist/lib/auth/validated-url.d.ts +6 -0
  157. package/dist/lib/auth/validated-url.js +53 -0
  158. package/dist/lib/config.d.ts +13 -3
  159. package/dist/lib/config.js +24 -19
  160. package/oclif.manifest.json +440 -71
  161. package/package.json +2 -1
  162. package/dist/lib/auth.d.ts +0 -43
  163. package/dist/lib/auth.js +0 -610
@@ -58,7 +58,7 @@ export default class BankIntegrations extends Command {
58
58
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
59
59
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
60
60
  query: Flags.string({ description: 'Additional query params as JSON' }),
61
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
61
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
62
62
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
63
63
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
64
64
  };
@@ -130,6 +130,8 @@ export default class BankIntegrations extends Command {
130
130
  queryParams.search = flags.search;
131
131
  if (flags['sort-by'])
132
132
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
133
+ if (flags.fields)
134
+ queryParams.fields = flags.fields;
133
135
  if (flags.query) {
134
136
  try {
135
137
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -332,11 +334,6 @@ export default class BankIntegrations extends Command {
332
334
  output = obj[arrayKeys[0]];
333
335
  }
334
336
  }
335
- // Filter fields if --fields flag is provided
336
- if (flags.fields) {
337
- const fieldList = flags.fields.split(',').map(f => f.trim());
338
- output = this.filterFields(output, fieldList);
339
- }
340
337
  // Sanitize output if requested
341
338
  if (flags.sanitize) {
342
339
  output = this.sanitizeOutput(output);
@@ -591,6 +588,8 @@ export default class BankIntegrations extends Command {
591
588
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
592
589
  if (flags.include)
593
590
  baseParams.include = flags.include;
591
+ if (flags.fields)
592
+ baseParams.fields = flags.fields;
594
593
  if (flags.query) {
595
594
  try {
596
595
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -645,12 +644,7 @@ export default class BankIntegrations extends Command {
645
644
  }
646
645
  }
647
646
  process.stderr.write(' done\n');
648
- // Filter fields if specified
649
647
  let output = allItems;
650
- if (flags.fields) {
651
- const fieldList = flags.fields.split(',').map(f => f.trim());
652
- output = this.filterFields(allItems, fieldList);
653
- }
654
648
  // Sanitize output if requested
655
649
  if (flags.sanitize) {
656
650
  output = this.sanitizeOutput(output);
@@ -979,56 +973,6 @@ export default class BankIntegrations extends Command {
979
973
  return `\$${op}`;
980
974
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
981
975
  }
982
- filterFields(data, fields) {
983
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
984
- const pick = (obj, keys) => {
985
- // Group keys by their first segment so sibling sub-paths
986
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
987
- // and don't clobber each other on merge.
988
- const directKeys = new Set();
989
- const nestedKeys = new Map();
990
- for (const key of keys) {
991
- if (key.split('.').some(isUnsafeKey))
992
- continue;
993
- if (key.includes('.')) {
994
- const [first, ...rest] = key.split('.');
995
- if (isUnsafeKey(first))
996
- continue;
997
- const list = nestedKeys.get(first);
998
- if (list) {
999
- list.push(rest.join('.'));
1000
- }
1001
- else {
1002
- nestedKeys.set(first, [rest.join('.')]);
1003
- }
1004
- }
1005
- else {
1006
- directKeys.add(key);
1007
- }
1008
- }
1009
- const result = {};
1010
- for (const key of directKeys) {
1011
- if (key in obj)
1012
- result[key] = obj[key];
1013
- }
1014
- for (const [first, restKeys] of nestedKeys) {
1015
- const val = obj[first];
1016
- if (!val || typeof val !== 'object')
1017
- continue;
1018
- if (Array.isArray(val)) {
1019
- result[first] = val.map(item => pick(item, restKeys));
1020
- }
1021
- else {
1022
- result[first] = pick(val, restKeys);
1023
- }
1024
- }
1025
- return result;
1026
- };
1027
- if (Array.isArray(data)) {
1028
- return data.map(item => pick(item, fields));
1029
- }
1030
- return pick(data, fields);
1031
- }
1032
976
  sanitizeOutput(data) {
1033
977
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1034
978
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -15,6 +15,7 @@ export default class BusinessPartners extends Command {
15
15
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
17
  'note-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'file-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  'business-partner-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
21
  limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -33,7 +34,6 @@ export default class BusinessPartners extends Command {
33
34
  private runCamt053Import;
34
35
  private runNestedImport;
35
36
  private parseFilterValue;
36
- private filterFields;
37
37
  private sanitizeOutput;
38
38
  private getColumns;
39
39
  private getValue;
@@ -5,8 +5,8 @@ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCred
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
- const NESTED_IMPORTS = ["notes", "contacts", "contacts-notes"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
8
+ const NESTED_IMPORTS = ["notes", "files", "contacts", "contacts-notes"];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/business-partners', summary: 'Get all business partners with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
12
  'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
@@ -23,6 +23,12 @@ const ENDPOINTS = {
23
23
  'get-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Get a specific note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
24
  'update-notes': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Update a note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
25
  'delete-notes': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Delete a note from a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
+ 'list-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files', summary: 'List file attachments for a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
+ 'create-files': { method: 'POST', path: '/v1/business-partners/{parentId}/files', summary: 'Upload a file attachment to a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
28
+ 'get-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Get a file attachment for a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
29
+ 'update-files': { method: 'PATCH', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a business partner', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
30
+ 'delete-files': { method: 'DELETE', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
31
+ 'files-download': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
32
  'list-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Get all contacts for a business partner with pagination', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
33
  'create-contacts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Create a new contact for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
28
34
  'get-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Get a contact by ID', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
@@ -57,6 +63,7 @@ export default class BusinessPartners extends Command {
57
63
  file: Flags.string({ char: 'f', description: 'Read request body from file' }),
58
64
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
59
65
  'note-id': Flags.string({ description: 'Path param: noteId' }),
66
+ 'file-id': Flags.string({ description: 'Path param: fileId' }),
60
67
  'business-partner-id': Flags.string({ description: 'Path param: businessPartnerId' }),
61
68
  page: Flags.string({ description: 'Page number' }),
62
69
  limit: Flags.string({ description: 'Items per page' }),
@@ -65,7 +72,7 @@ export default class BusinessPartners extends Command {
65
72
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
66
73
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
67
74
  query: Flags.string({ description: 'Additional query params as JSON' }),
68
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
75
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
69
76
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
70
77
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
71
78
  };
@@ -137,6 +144,8 @@ export default class BusinessPartners extends Command {
137
144
  queryParams.search = flags.search;
138
145
  if (flags['sort-by'])
139
146
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
147
+ if (flags.fields)
148
+ queryParams.fields = flags.fields;
140
149
  if (flags.query) {
141
150
  try {
142
151
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -339,11 +348,6 @@ export default class BusinessPartners extends Command {
339
348
  output = obj[arrayKeys[0]];
340
349
  }
341
350
  }
342
- // Filter fields if --fields flag is provided
343
- if (flags.fields) {
344
- const fieldList = flags.fields.split(',').map(f => f.trim());
345
- output = this.filterFields(output, fieldList);
346
- }
347
351
  // Sanitize output if requested
348
352
  if (flags.sanitize) {
349
353
  output = this.sanitizeOutput(output);
@@ -598,6 +602,8 @@ export default class BusinessPartners extends Command {
598
602
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
599
603
  if (flags.include)
600
604
  baseParams.include = flags.include;
605
+ if (flags.fields)
606
+ baseParams.fields = flags.fields;
601
607
  if (flags.query) {
602
608
  try {
603
609
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -652,12 +658,7 @@ export default class BusinessPartners extends Command {
652
658
  }
653
659
  }
654
660
  process.stderr.write(' done\n');
655
- // Filter fields if specified
656
661
  let output = allItems;
657
- if (flags.fields) {
658
- const fieldList = flags.fields.split(',').map(f => f.trim());
659
- output = this.filterFields(allItems, fieldList);
660
- }
661
662
  // Sanitize output if requested
662
663
  if (flags.sanitize) {
663
664
  output = this.sanitizeOutput(output);
@@ -986,56 +987,6 @@ export default class BusinessPartners extends Command {
986
987
  return `\$${op}`;
987
988
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
988
989
  }
989
- filterFields(data, fields) {
990
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
991
- const pick = (obj, keys) => {
992
- // Group keys by their first segment so sibling sub-paths
993
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
994
- // and don't clobber each other on merge.
995
- const directKeys = new Set();
996
- const nestedKeys = new Map();
997
- for (const key of keys) {
998
- if (key.split('.').some(isUnsafeKey))
999
- continue;
1000
- if (key.includes('.')) {
1001
- const [first, ...rest] = key.split('.');
1002
- if (isUnsafeKey(first))
1003
- continue;
1004
- const list = nestedKeys.get(first);
1005
- if (list) {
1006
- list.push(rest.join('.'));
1007
- }
1008
- else {
1009
- nestedKeys.set(first, [rest.join('.')]);
1010
- }
1011
- }
1012
- else {
1013
- directKeys.add(key);
1014
- }
1015
- }
1016
- const result = {};
1017
- for (const key of directKeys) {
1018
- if (key in obj)
1019
- result[key] = obj[key];
1020
- }
1021
- for (const [first, restKeys] of nestedKeys) {
1022
- const val = obj[first];
1023
- if (!val || typeof val !== 'object')
1024
- continue;
1025
- if (Array.isArray(val)) {
1026
- result[first] = val.map(item => pick(item, restKeys));
1027
- }
1028
- else {
1029
- result[first] = pick(val, restKeys);
1030
- }
1031
- }
1032
- return result;
1033
- };
1034
- if (Array.isArray(data)) {
1035
- return data.map(item => pick(item, fields));
1036
- }
1037
- return pick(data, fields);
1038
- }
1039
990
  sanitizeOutput(data) {
1040
991
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1041
992
  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 Chat 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;
@@ -42,7 +42,7 @@ export default class Chat extends Command {
42
42
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
43
43
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
44
44
  query: Flags.string({ description: 'Additional query params as JSON' }),
45
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
45
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
46
46
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
47
47
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
48
48
  };
@@ -114,6 +114,8 @@ export default class Chat extends Command {
114
114
  queryParams.search = flags.search;
115
115
  if (flags['sort-by'])
116
116
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
117
+ if (flags.fields)
118
+ queryParams.fields = flags.fields;
117
119
  if (flags.query) {
118
120
  try {
119
121
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -316,11 +318,6 @@ export default class Chat extends Command {
316
318
  output = obj[arrayKeys[0]];
317
319
  }
318
320
  }
319
- // Filter fields if --fields flag is provided
320
- if (flags.fields) {
321
- const fieldList = flags.fields.split(',').map(f => f.trim());
322
- output = this.filterFields(output, fieldList);
323
- }
324
321
  // Sanitize output if requested
325
322
  if (flags.sanitize) {
326
323
  output = this.sanitizeOutput(output);
@@ -575,6 +572,8 @@ export default class Chat extends Command {
575
572
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
576
573
  if (flags.include)
577
574
  baseParams.include = flags.include;
575
+ if (flags.fields)
576
+ baseParams.fields = flags.fields;
578
577
  if (flags.query) {
579
578
  try {
580
579
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -629,12 +628,7 @@ export default class Chat extends Command {
629
628
  }
630
629
  }
631
630
  process.stderr.write(' done\n');
632
- // Filter fields if specified
633
631
  let output = allItems;
634
- if (flags.fields) {
635
- const fieldList = flags.fields.split(',').map(f => f.trim());
636
- output = this.filterFields(allItems, fieldList);
637
- }
638
632
  // Sanitize output if requested
639
633
  if (flags.sanitize) {
640
634
  output = this.sanitizeOutput(output);
@@ -963,56 +957,6 @@ export default class Chat extends Command {
963
957
  return `\$${op}`;
964
958
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
965
959
  }
966
- filterFields(data, fields) {
967
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
968
- const pick = (obj, keys) => {
969
- // Group keys by their first segment so sibling sub-paths
970
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
971
- // and don't clobber each other on merge.
972
- const directKeys = new Set();
973
- const nestedKeys = new Map();
974
- for (const key of keys) {
975
- if (key.split('.').some(isUnsafeKey))
976
- continue;
977
- if (key.includes('.')) {
978
- const [first, ...rest] = key.split('.');
979
- if (isUnsafeKey(first))
980
- continue;
981
- const list = nestedKeys.get(first);
982
- if (list) {
983
- list.push(rest.join('.'));
984
- }
985
- else {
986
- nestedKeys.set(first, [rest.join('.')]);
987
- }
988
- }
989
- else {
990
- directKeys.add(key);
991
- }
992
- }
993
- const result = {};
994
- for (const key of directKeys) {
995
- if (key in obj)
996
- result[key] = obj[key];
997
- }
998
- for (const [first, restKeys] of nestedKeys) {
999
- const val = obj[first];
1000
- if (!val || typeof val !== 'object')
1001
- continue;
1002
- if (Array.isArray(val)) {
1003
- result[first] = val.map(item => pick(item, restKeys));
1004
- }
1005
- else {
1006
- result[first] = pick(val, restKeys);
1007
- }
1008
- }
1009
- return result;
1010
- };
1011
- if (Array.isArray(data)) {
1012
- return data.map(item => pick(item, fields));
1013
- }
1014
- return pick(data, fields);
1015
- }
1016
960
  sanitizeOutput(data) {
1017
961
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1018
962
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -9,6 +9,7 @@ export default class ConfigAdd extends Command {
9
9
  'client-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ 'keycloak-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  };
14
15
  run(): Promise<void>;
@@ -18,5 +19,7 @@ export default class ConfigAdd extends Command {
18
19
  * OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
19
20
  * scripted `config add` calls working exactly as before API-key support.
20
21
  */
22
+ /** Validates `--keycloak-url` before the context is stored. */
23
+ private resolveKeycloakBaseUrl;
21
24
  private resolveAuthType;
22
25
  }
@@ -1,6 +1,7 @@
1
1
  import { confirm, input, password, select } from '@inquirer/prompts';
2
2
  import { Command, Flags } from '@oclif/core';
3
- import { addContext, DEFAULT_CLIENT_IDS, getContext, } from '../../lib/config.js';
3
+ import { keycloakBaseUrlOf, parseKeycloakBaseUrl } from '../../lib/auth/index.js';
4
+ import { addContext, DEFAULT_KEYCLOAK_CLIENT_ID, getContext, } from '../../lib/config.js';
4
5
  export default class ConfigAdd extends Command {
5
6
  static description = 'Add a new context configuration';
6
7
  static examples = [
@@ -8,6 +9,7 @@ export default class ConfigAdd extends Command {
8
9
  '<%= config.bin %> config add --name my-client --environment live',
9
10
  '<%= config.bin %> config add --name dev-context --environment dev --client-id abc123',
10
11
  '<%= config.bin %> config add --name ci --environment live --auth-type api-key --api-key <key>',
12
+ '<%= config.bin %> config add --name test-kc --environment test --keycloak-url https://iam-test.aderis.com',
11
13
  ];
12
14
  static flags = {
13
15
  'api-key': Flags.string({
@@ -25,14 +27,17 @@ export default class ConfigAdd extends Command {
25
27
  }),
26
28
  environment: Flags.string({
27
29
  char: 'e',
28
- description: 'Environment (dev, staging, live)',
29
- options: ['dev', 'staging', 'live'],
30
+ description: 'Environment (dev, test, staging, live)',
31
+ options: ['dev', 'test', 'staging', 'live'],
30
32
  }),
31
33
  force: Flags.boolean({
32
34
  char: 'f',
33
35
  default: false,
34
36
  description: 'Overwrite existing context without prompting',
35
37
  }),
38
+ 'keycloak-url': Flags.string({
39
+ description: 'Keycloak origin to use instead of the default host for the environment (https, allowlisted). The realm stays that of the environment',
40
+ }),
36
41
  name: Flags.string({
37
42
  char: 'n',
38
43
  description: 'Context name',
@@ -74,6 +79,7 @@ export default class ConfigAdd extends Command {
74
79
  choices: [
75
80
  { name: 'live (production)', value: 'live' },
76
81
  { name: 'staging', value: 'staging' },
82
+ { name: 'test (QA)', value: 'test' },
77
83
  { name: 'dev (development)', value: 'dev' },
78
84
  ],
79
85
  message: 'Environment:',
@@ -87,6 +93,9 @@ export default class ConfigAdd extends Command {
87
93
  if (flags['client-id'] || flags['client-secret']) {
88
94
  this.error('--client-id and --client-secret only apply to OAuth contexts; omit them with --auth-type api-key.');
89
95
  }
96
+ if (flags['keycloak-url']) {
97
+ this.error('--keycloak-url only applies to OAuth contexts; omit it with --auth-type api-key.');
98
+ }
90
99
  const apiKey = flags['api-key'] ??
91
100
  (await password({
92
101
  mask: true,
@@ -108,25 +117,21 @@ export default class ConfigAdd extends Command {
108
117
  this.log(`\nNo login needed — the CLI is ready to use.`);
109
118
  return;
110
119
  }
111
- // Get client ID
120
+ const keycloakBaseUrl = this.resolveKeycloakBaseUrl(flags['keycloak-url']);
121
+ // Without a terminal the default client id is taken without confirmation.
112
122
  let clientId = flags['client-id'];
113
123
  if (!clientId) {
114
- const defaultClientId = DEFAULT_CLIENT_IDS[environment];
115
- if (defaultClientId) {
116
- const useDefault = await confirm({
124
+ const useDefault = !process.stdin.isTTY ||
125
+ (await confirm({
117
126
  default: true,
118
- message: `Use default client ID for ${environment}?`,
119
- });
120
- if (useDefault) {
121
- clientId = defaultClientId;
122
- }
123
- }
124
- if (!clientId) {
125
- clientId = await input({
127
+ message: `Use default client ID (${DEFAULT_KEYCLOAK_CLIENT_ID})?`,
128
+ }));
129
+ clientId = useDefault
130
+ ? DEFAULT_KEYCLOAK_CLIENT_ID
131
+ : await input({
126
132
  message: 'OAuth client ID:',
127
133
  validate: (value) => (value.trim() ? true : 'Client ID is required'),
128
134
  });
129
- }
130
135
  }
131
136
  // Get client secret (optional)
132
137
  let clientSecret = flags['client-secret'];
@@ -148,10 +153,12 @@ export default class ConfigAdd extends Command {
148
153
  client_id: clientId,
149
154
  client_secret: clientSecret,
150
155
  environment,
156
+ keycloak_base_url: keycloakBaseUrl,
151
157
  name,
152
158
  });
153
159
  this.log(`\nContext "${name}" created successfully.`);
154
160
  this.log(`Environment: ${environment}`);
161
+ this.log(`Keycloak URL: ${keycloakBaseUrlOf({ environment, keycloak_base_url: keycloakBaseUrl })}`);
155
162
  this.log(`Client ID: ${clientId}`);
156
163
  this.log(`\nTo authenticate, run: aderis auth login`);
157
164
  }
@@ -161,6 +168,17 @@ export default class ConfigAdd extends Command {
161
168
  * OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
162
169
  * scripted `config add` calls working exactly as before API-key support.
163
170
  */
171
+ /** Validates `--keycloak-url` before the context is stored. */
172
+ resolveKeycloakBaseUrl(flag) {
173
+ if (flag === undefined)
174
+ return undefined;
175
+ try {
176
+ return parseKeycloakBaseUrl(flag);
177
+ }
178
+ catch (error) {
179
+ this.error(`Invalid --keycloak-url: ${error instanceof Error ? error.message : String(error)}`);
180
+ }
181
+ }
164
182
  async resolveAuthType(flags) {
165
183
  if (flags['auth-type']) {
166
184
  return flags['auth-type'] === 'api-key' ? 'api_key' : 'oauth2';
@@ -1,4 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import { keycloakBaseUrlOf } from '../../lib/auth/index.js';
2
3
  import { getAuthType, getCurrentContext, hasCredentials } from '../../lib/config.js';
3
4
  export default class ConfigCurrent extends Command {
4
5
  static description = 'Show the current context';
@@ -28,6 +29,7 @@ export default class ConfigCurrent extends Command {
28
29
  authenticated: hasCredentials(context),
29
30
  client_id: context.client_id,
30
31
  environment: context.environment,
32
+ keycloak_base_url: getAuthType(context) === 'oauth2' ? keycloakBaseUrlOf(context) : undefined,
31
33
  name: context.name,
32
34
  tenant_id: context.tenant_id,
33
35
  }, null, 2));
@@ -38,6 +40,7 @@ export default class ConfigCurrent extends Command {
38
40
  this.log(`Environment: ${context.environment}`);
39
41
  this.log(`Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
40
42
  if (authType === 'oauth2') {
43
+ this.log(`Keycloak URL: ${keycloakBaseUrlOf(context)}`);
41
44
  this.log(`Client ID: ${context.client_id ?? 'N/A'}`);
42
45
  }
43
46
  if (context.tenant_id) {
@@ -1,4 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import { keycloakBaseUrlOf } from '../../lib/auth/index.js';
2
3
  import { getAuthType, getCurrentContext, hasCredentials, listContexts } from '../../lib/config.js';
3
4
  export default class ConfigList extends Command {
4
5
  static description = 'List all configured contexts';
@@ -20,6 +21,7 @@ export default class ConfigList extends Command {
20
21
  authenticated: hasCredentials(c),
21
22
  client_id: c.client_id,
22
23
  environment: c.environment,
24
+ keycloak_base_url: getAuthType(c) === 'oauth2' ? keycloakBaseUrlOf(c) : undefined,
23
25
  name: c.name,
24
26
  tenant_id: c.tenant_id,
25
27
  })),
@@ -42,6 +44,7 @@ export default class ConfigList extends Command {
42
44
  this.log(` Environment: ${ctx.environment}`);
43
45
  this.log(` Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
44
46
  if (authType === 'oauth2') {
47
+ this.log(` Keycloak URL: ${keycloakBaseUrlOf(ctx)}`);
45
48
  this.log(` Client ID: ${ctx.client_id ?? 'N/A'}`);
46
49
  }
47
50
  this.log(` Authenticated: ${auth}`);
@@ -33,7 +33,6 @@ export default class CreditCardAccounts extends Command {
33
33
  private runCamt053Import;
34
34
  private runNestedImport;
35
35
  private parseFilterValue;
36
- private filterFields;
37
36
  private sanitizeOutput;
38
37
  private getColumns;
39
38
  private getValue;