@aderis-erp/cli 1.0.15 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +30 -8
  2. package/dist/commands/access-groups.d.ts +0 -1
  3. package/dist/commands/access-groups.js +54 -78
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +51 -75
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +80 -94
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +59 -83
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +58 -82
  12. package/dist/commands/agent-runs.d.ts +39 -0
  13. package/dist/commands/agent-runs.js +1155 -0
  14. package/dist/commands/analytics.d.ts +0 -1
  15. package/dist/commands/analytics.js +52 -76
  16. package/dist/commands/{text-templates.d.ts → api-keys.d.ts} +1 -2
  17. package/dist/commands/api-keys.js +1150 -0
  18. package/dist/commands/async-jobs.d.ts +39 -0
  19. package/dist/commands/async-jobs.js +1158 -0
  20. package/dist/commands/audit-log.d.ts +0 -1
  21. package/dist/commands/audit-log.js +51 -75
  22. package/dist/commands/auth/login.d.ts +9 -0
  23. package/dist/commands/auth/login.js +73 -20
  24. package/dist/commands/auth/logout.d.ts +4 -0
  25. package/dist/commands/auth/logout.js +4 -0
  26. package/dist/commands/auth/status.js +26 -17
  27. package/dist/commands/auth/tenant.js +3 -6
  28. package/dist/commands/auth.d.ts +0 -1
  29. package/dist/commands/auth.js +5 -34
  30. package/dist/commands/bank-accounts.d.ts +0 -1
  31. package/dist/commands/bank-accounts.js +68 -91
  32. package/dist/commands/bank-integrations.d.ts +1 -1
  33. package/dist/commands/bank-integrations.js +71 -92
  34. package/dist/commands/business-partners.d.ts +1 -1
  35. package/dist/commands/business-partners.js +84 -100
  36. package/dist/commands/chat.d.ts +0 -1
  37. package/dist/commands/chat.js +57 -80
  38. package/dist/commands/config/add.d.ts +3 -0
  39. package/dist/commands/config/add.js +34 -16
  40. package/dist/commands/config/current.js +3 -0
  41. package/dist/commands/config/list.js +3 -0
  42. package/dist/commands/credit-card-accounts.d.ts +1 -1
  43. package/dist/commands/credit-card-accounts.js +72 -90
  44. package/dist/commands/delivery-notes.d.ts +0 -1
  45. package/dist/commands/delivery-notes.js +83 -105
  46. package/dist/commands/dunning-configs.d.ts +0 -1
  47. package/dist/commands/dunning-configs.js +54 -78
  48. package/dist/commands/dunning-reminders.d.ts +0 -1
  49. package/dist/commands/dunning-reminders.js +57 -81
  50. package/dist/commands/dunning-runs.d.ts +0 -1
  51. package/dist/commands/dunning-runs.js +50 -74
  52. package/dist/commands/email-transactions.d.ts +0 -1
  53. package/dist/commands/email-transactions.js +52 -76
  54. package/dist/commands/employees.d.ts +0 -1
  55. package/dist/commands/employees.js +81 -105
  56. package/dist/commands/entity-configurations.d.ts +0 -1
  57. package/dist/commands/entity-configurations.js +52 -76
  58. package/dist/commands/event-processor-mappings.d.ts +0 -1
  59. package/dist/commands/event-processor-mappings.js +54 -78
  60. package/dist/commands/files.d.ts +0 -1
  61. package/dist/commands/files.js +61 -85
  62. package/dist/commands/health.d.ts +0 -1
  63. package/dist/commands/health.js +54 -78
  64. package/dist/commands/imports.d.ts +39 -0
  65. package/dist/commands/imports.js +1151 -0
  66. package/dist/commands/inbox.d.ts +0 -1
  67. package/dist/commands/inbox.js +57 -80
  68. package/dist/commands/integration-identities.d.ts +40 -0
  69. package/dist/commands/integration-identities.js +1151 -0
  70. package/dist/commands/integrations.d.ts +0 -1
  71. package/dist/commands/integrations.js +52 -76
  72. package/dist/commands/item-categories.d.ts +0 -1
  73. package/dist/commands/item-categories.js +54 -78
  74. package/dist/commands/items.d.ts +0 -1
  75. package/dist/commands/items.js +54 -78
  76. package/dist/commands/journal-lines.d.ts +0 -1
  77. package/dist/commands/journal-lines.js +50 -74
  78. package/dist/commands/journals.d.ts +1 -1
  79. package/dist/commands/journals.js +75 -96
  80. package/dist/commands/ledgers.d.ts +3 -2
  81. package/dist/commands/ledgers.js +100 -95
  82. package/dist/commands/my-account.d.ts +39 -0
  83. package/dist/commands/my-account.js +1160 -0
  84. package/dist/commands/office-locations.d.ts +0 -1
  85. package/dist/commands/office-locations.js +54 -78
  86. package/dist/commands/payment-methods.d.ts +0 -1
  87. package/dist/commands/payment-methods.js +59 -83
  88. package/dist/commands/payment-runs.d.ts +0 -1
  89. package/dist/commands/payment-runs.js +66 -90
  90. package/dist/commands/payment-terms.d.ts +0 -1
  91. package/dist/commands/payment-terms.js +55 -79
  92. package/dist/commands/payments.d.ts +0 -1
  93. package/dist/commands/payments.js +56 -80
  94. package/dist/commands/payroll-runs.d.ts +43 -0
  95. package/dist/commands/payroll-runs.js +1188 -0
  96. package/dist/commands/permissions.d.ts +39 -0
  97. package/dist/commands/permissions.js +1146 -0
  98. package/dist/commands/price-lists.d.ts +0 -1
  99. package/dist/commands/price-lists.js +59 -83
  100. package/dist/commands/processor-mappings.d.ts +0 -1
  101. package/dist/commands/processor-mappings.js +53 -77
  102. package/dist/commands/processors.d.ts +0 -1
  103. package/dist/commands/processors.js +60 -85
  104. package/dist/commands/purchase-order-types.d.ts +0 -1
  105. package/dist/commands/purchase-order-types.js +54 -78
  106. package/dist/commands/purchase-orders.d.ts +0 -1
  107. package/dist/commands/purchase-orders.js +74 -97
  108. package/dist/commands/reports.d.ts +0 -1
  109. package/dist/commands/reports.js +54 -78
  110. package/dist/commands/roles.d.ts +39 -0
  111. package/dist/commands/{text-templates.js → roles.js} +64 -90
  112. package/dist/commands/salary-types.d.ts +0 -1
  113. package/dist/commands/salary-types.js +54 -78
  114. package/dist/commands/sales-invoices.d.ts +0 -1
  115. package/dist/commands/sales-invoices.js +90 -110
  116. package/dist/commands/sales-orders.d.ts +0 -1
  117. package/dist/commands/sales-orders.js +83 -105
  118. package/dist/commands/saved-views.d.ts +0 -1
  119. package/dist/commands/saved-views.js +52 -76
  120. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  121. package/dist/commands/scheduled-processor-mappings.js +54 -78
  122. package/dist/commands/schedules.d.ts +0 -1
  123. package/dist/commands/schedules.js +58 -82
  124. package/dist/commands/switch.js +4 -4
  125. package/dist/commands/tag-configurations.d.ts +0 -1
  126. package/dist/commands/tag-configurations.js +56 -79
  127. package/dist/commands/tags.d.ts +0 -1
  128. package/dist/commands/tags.js +54 -78
  129. package/dist/commands/tax-codes.d.ts +0 -1
  130. package/dist/commands/tax-codes.js +57 -81
  131. package/dist/commands/tax-matrix.d.ts +0 -1
  132. package/dist/commands/tax-matrix.js +54 -78
  133. package/dist/commands/templates.d.ts +39 -0
  134. package/dist/commands/templates.js +1157 -0
  135. package/dist/commands/tenants.d.ts +0 -1
  136. package/dist/commands/tenants.js +58 -81
  137. package/dist/commands/tools.d.ts +0 -1
  138. package/dist/commands/tools.js +51 -75
  139. package/dist/commands/users.d.ts +42 -0
  140. package/dist/commands/users.js +1174 -0
  141. package/dist/commands/vat-declarations.d.ts +3 -1
  142. package/dist/commands/vat-declarations.js +78 -93
  143. package/dist/commands/vendor-invoices.d.ts +0 -1
  144. package/dist/commands/vendor-invoices.js +87 -110
  145. package/dist/commands/webhooks.d.ts +0 -1
  146. package/dist/commands/webhooks.js +56 -80
  147. package/dist/commands/workflows.d.ts +0 -1
  148. package/dist/commands/workflows.js +52 -76
  149. package/dist/lib/api-client.d.ts +6 -0
  150. package/dist/lib/api-client.js +16 -6
  151. package/dist/lib/auth/constants.d.ts +11 -0
  152. package/dist/lib/auth/constants.js +11 -0
  153. package/dist/lib/auth/errors.d.ts +17 -0
  154. package/dist/lib/auth/errors.js +22 -0
  155. package/dist/lib/auth/flow.d.ts +27 -0
  156. package/dist/lib/auth/flow.js +163 -0
  157. package/dist/lib/auth/guards.d.ts +5 -0
  158. package/dist/lib/auth/guards.js +14 -0
  159. package/dist/lib/auth/http.d.ts +16 -0
  160. package/dist/lib/auth/http.js +49 -0
  161. package/dist/lib/auth/index.d.ts +49 -0
  162. package/dist/lib/auth/index.js +228 -0
  163. package/dist/lib/auth/jwks.d.ts +19 -0
  164. package/dist/lib/auth/jwks.js +112 -0
  165. package/dist/lib/auth/keycloak-provider.d.ts +91 -0
  166. package/dist/lib/auth/keycloak-provider.js +244 -0
  167. package/dist/lib/auth/pages.d.ts +25 -0
  168. package/dist/lib/auth/pages.js +123 -0
  169. package/dist/lib/auth/pkce.d.ts +11 -0
  170. package/dist/lib/auth/pkce.js +17 -0
  171. package/dist/lib/auth/tokens.d.ts +59 -0
  172. package/dist/lib/auth/tokens.js +105 -0
  173. package/dist/lib/auth/types.d.ts +61 -0
  174. package/dist/lib/auth/types.js +1 -0
  175. package/dist/lib/auth/validated-url.d.ts +6 -0
  176. package/dist/lib/auth/validated-url.js +53 -0
  177. package/dist/lib/config.d.ts +13 -3
  178. package/dist/lib/config.js +24 -19
  179. package/oclif.manifest.json +2447 -641
  180. package/package.json +5 -4
  181. package/dist/lib/auth.d.ts +0 -43
  182. package/dist/lib/auth.js +0 -610
@@ -1,30 +1,31 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["ledger-mappings", "notes"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "get-balances", "get-balances-latest", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "get-balances", "get-balances-latest", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-daily-balances"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/bank-accounts', summary: 'Get all bank accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/bank-accounts', summary: 'Create a new bank account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentRunDays', 'notes'] },
13
- 'get': { method: 'GET', path: '/v1/bank-accounts/{id}', summary: 'Get a bank account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'update': { method: 'PATCH', path: '/v1/bank-accounts/{id}', summary: 'Update a bank account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentRunDays', 'notes'] },
15
- 'delete': { method: 'DELETE', path: '/v1/bank-accounts/{id}', summary: 'Delete a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'get-balances': { method: 'GET', path: '/v1/bank-accounts/{id}/balances', summary: 'Get balance history for a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
- 'get-balances-latest': { method: 'GET', path: '/v1/bank-accounts/{id}/balances/latest', summary: 'Get latest balances (OPBD, CLBD, CLAV) for a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
- 'list-ledger-mappings': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
- 'create-ledger-mappings': { method: 'POST', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
- 'get-ledger-mappings': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['bankAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
- 'update-ledger-mappings': { method: 'PATCH', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['bankAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
- 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['bankAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
- 'list-notes': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/notes', summary: 'Get all notes for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
- 'create-notes': { method: 'POST', path: '/v1/bank-accounts/{bankAccountId}/notes', summary: 'Create a note for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
- 'get-notes': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Get a specific note for a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
- 'update-notes': { method: 'PATCH', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Update a note for a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
- 'delete-notes': { method: 'DELETE', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Delete a note from a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'list': { method: 'GET', path: '/v1/bank-accounts', summary: 'Get all bank accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/bank-accounts', summary: 'Create a new bank account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentRunDays', 'notes'] },
13
+ 'get': { method: 'GET', path: '/v1/bank-accounts/{id}', summary: 'Get a bank account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/bank-accounts/{id}', summary: 'Update a bank account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentRunDays', 'notes'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/bank-accounts/{id}', summary: 'Delete a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
+ 'get-balances': { method: 'GET', path: '/v1/bank-accounts/{id}/balances', summary: 'Get balance history for a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
+ 'get-balances-latest': { method: 'GET', path: '/v1/bank-accounts/{id}/balances/latest', summary: 'Get latest balances (OPBD, CLBD, CLAV) for a bank account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
+ 'list-ledger-mappings': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
+ 'create-ledger-mappings': { method: 'POST', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
+ 'get-ledger-mappings': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['bankAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
+ 'update-ledger-mappings': { method: 'PATCH', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['bankAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
+ 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/bank-accounts/{bankAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['bankAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
+ 'list-notes': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/notes', summary: 'Get all notes for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
+ 'create-notes': { method: 'POST', path: '/v1/bank-accounts/{bankAccountId}/notes', summary: 'Create a note for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
+ 'get-notes': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Get a specific note for a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
+ 'update-notes': { method: 'PATCH', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Update a note for a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
27
+ 'delete-notes': { method: 'DELETE', path: '/v1/bank-accounts/{bankAccountId}/notes/{noteId}', summary: 'Delete a note from a bank account', pathParams: ['bankAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
+ 'list-daily-balances': { method: 'GET', path: '/v1/bank-accounts/{bankAccountId}/daily-balances', summary: 'Get a bank account\'s daily closing balances for a date range', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
29
  };
29
30
  export default class BankAccounts extends Command {
30
31
  static description = 'Manage bank accounts';
@@ -57,7 +58,7 @@ export default class BankAccounts extends Command {
57
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.' }),
58
59
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
59
60
  query: Flags.string({ description: 'Additional query params as JSON' }),
60
- 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)' }),
61
62
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
62
63
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
63
64
  };
@@ -129,6 +130,8 @@ export default class BankAccounts extends Command {
129
130
  queryParams.search = flags.search;
130
131
  if (flags['sort-by'])
131
132
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
133
+ if (flags.fields)
134
+ queryParams.fields = flags.fields;
132
135
  if (flags.query) {
133
136
  try {
134
137
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -276,10 +279,11 @@ export default class BankAccounts extends Command {
276
279
  process.stderr.write('\n');
277
280
  }
278
281
  try {
279
- // Binary response: download to file
282
+ // Binary response: to -f / --file, or to stdout when it is redirected.
280
283
  if (endpoint.isBinaryResponse) {
281
- if (!flags.file) {
282
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
284
+ const toStdout = !flags.file;
285
+ if (toStdout && process.stdout.isTTY) {
286
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
283
287
  }
284
288
  const binResponse = await apiRequest(endpoint.method, apiPath, {
285
289
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -287,24 +291,55 @@ export default class BankAccounts extends Command {
287
291
  contextName: flags.context,
288
292
  binary: true,
289
293
  });
290
- const fsModule = await import('node:fs');
291
294
  const binData = binResponse.data;
292
295
  const buffer = Buffer.isBuffer(binData)
293
296
  ? binData
294
297
  : Buffer.from(binData);
298
+ if (toStdout) {
299
+ // Nothing else may reach stdout here — it is the file.
300
+ process.stdout.write(buffer);
301
+ return;
302
+ }
303
+ const fsModule = await import('node:fs');
295
304
  fsModule.writeFileSync(flags.file, buffer);
296
305
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
297
306
  return;
298
307
  }
299
- const response = await apiRequest(endpoint.method, apiPath, {
300
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
301
- data,
302
- contextName: flags.context,
303
- acceptAny: true,
304
- });
308
+ let response;
309
+ if (endpoint.hasBinaryVariant) {
310
+ // This route answers JSON or a file depending on the request body, so the
311
+ // bytes are read first and the response's own content-type decides. Asking
312
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
313
+ const raw = await apiRequest(endpoint.method, apiPath, {
314
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
315
+ data,
316
+ contextName: flags.context,
317
+ binary: true,
318
+ });
319
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
320
+ const rawData = raw.data;
321
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
322
+ if (!isTextualMediaType(rawType)) {
323
+ if (process.stdout.isTTY) {
324
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
325
+ }
326
+ // Nothing else may reach stdout here — it is the file.
327
+ process.stdout.write(buffer);
328
+ return;
329
+ }
330
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
331
+ }
332
+ else {
333
+ response = await apiRequest(endpoint.method, apiPath, {
334
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
335
+ data,
336
+ contextName: flags.context,
337
+ acceptAny: true,
338
+ });
339
+ }
305
340
  // Check content-type for non-JSON responses (XML, text, etc.)
306
341
  const contentType = (response.headers['content-type'] || '').toLowerCase();
307
- if (contentType.includes('xml') || contentType.includes('text/')) {
342
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
308
343
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
309
344
  this.log(String(response.data));
310
345
  return;
@@ -331,11 +366,6 @@ export default class BankAccounts extends Command {
331
366
  output = obj[arrayKeys[0]];
332
367
  }
333
368
  }
334
- // Filter fields if --fields flag is provided
335
- if (flags.fields) {
336
- const fieldList = flags.fields.split(',').map(f => f.trim());
337
- output = this.filterFields(output, fieldList);
338
- }
339
369
  // Sanitize output if requested
340
370
  if (flags.sanitize) {
341
371
  output = this.sanitizeOutput(output);
@@ -590,6 +620,8 @@ export default class BankAccounts extends Command {
590
620
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
591
621
  if (flags.include)
592
622
  baseParams.include = flags.include;
623
+ if (flags.fields)
624
+ baseParams.fields = flags.fields;
593
625
  if (flags.query) {
594
626
  try {
595
627
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -644,12 +676,7 @@ export default class BankAccounts extends Command {
644
676
  }
645
677
  }
646
678
  process.stderr.write(' done\n');
647
- // Filter fields if specified
648
679
  let output = allItems;
649
- if (flags.fields) {
650
- const fieldList = flags.fields.split(',').map(f => f.trim());
651
- output = this.filterFields(allItems, fieldList);
652
- }
653
680
  // Sanitize output if requested
654
681
  if (flags.sanitize) {
655
682
  output = this.sanitizeOutput(output);
@@ -978,56 +1005,6 @@ export default class BankAccounts extends Command {
978
1005
  return `\$${op}`;
979
1006
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
980
1007
  }
981
- filterFields(data, fields) {
982
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
983
- const pick = (obj, keys) => {
984
- // Group keys by their first segment so sibling sub-paths
985
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
986
- // and don't clobber each other on merge.
987
- const directKeys = new Set();
988
- const nestedKeys = new Map();
989
- for (const key of keys) {
990
- if (key.split('.').some(isUnsafeKey))
991
- continue;
992
- if (key.includes('.')) {
993
- const [first, ...rest] = key.split('.');
994
- if (isUnsafeKey(first))
995
- continue;
996
- const list = nestedKeys.get(first);
997
- if (list) {
998
- list.push(rest.join('.'));
999
- }
1000
- else {
1001
- nestedKeys.set(first, [rest.join('.')]);
1002
- }
1003
- }
1004
- else {
1005
- directKeys.add(key);
1006
- }
1007
- }
1008
- const result = {};
1009
- for (const key of directKeys) {
1010
- if (key in obj)
1011
- result[key] = obj[key];
1012
- }
1013
- for (const [first, restKeys] of nestedKeys) {
1014
- const val = obj[first];
1015
- if (!val || typeof val !== 'object')
1016
- continue;
1017
- if (Array.isArray(val)) {
1018
- result[first] = val.map(item => pick(item, restKeys));
1019
- }
1020
- else {
1021
- result[first] = pick(val, restKeys);
1022
- }
1023
- }
1024
- return result;
1025
- };
1026
- if (Array.isArray(data)) {
1027
- return data.map(item => pick(item, fields));
1028
- }
1029
- return pick(data, fields);
1030
- }
1031
1008
  sanitizeOutput(data) {
1032
1009
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1033
1010
  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 BankIntegrations 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
  'import-run-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
  'statement-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 BankIntegrations 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;
@@ -1,31 +1,33 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["initiate", "providers-supported", "providers-consent-status", "delete-providers-consent", "list", "create", "get", "update", "delete", "log", "list-account-statement-runs", "trigger-account-statement-runs", "get-account-statement-runs", "account-statement-runs-files", "trigger-account-statement-runs-reimport", "list-statements", "statements", "download-statements-xml"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["initiate", "providers-supported", "providers-consent-status", "delete-providers-consent", "list", "create", "get", "update", "delete", "log", "list-account-statement-runs", "trigger-account-statement-runs", "get-account-statement-runs", "account-statement-runs-files", "trigger-account-statement-runs-reimport", "account-statement-runs-files-download", "list-all", "list-statements", "statements", "download-statements-xml"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'initiate': { method: 'POST', path: '/v1/bank-integrations/initiate', summary: 'Initiate the OAuth/auth flow for a bank integration', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'providers-supported': { method: 'GET', path: '/v1/bank-integrations/providers/supported', summary: 'List supported bLink providers', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
- 'providers-consent-status': { method: 'GET', path: '/v1/bank-integrations/providers/consent-status', summary: 'Get bLink consent status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'delete-providers-consent': { method: 'DELETE', path: '/v1/bank-integrations/providers/{providerId}/consent', summary: 'Revoke bLink consent for a provider', pathParams: ['providerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- 'list': { method: 'GET', path: '/v1/bank-integrations', summary: 'List all BankIntegrations for the current tenant with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'create': { method: 'POST', path: '/v1/bank-integrations', summary: 'Create a BankIntegration record', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['methods'] },
17
- 'get': { method: 'GET', path: '/v1/bank-integrations/{id}', summary: 'Get a BankIntegration by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
- 'update': { method: 'PATCH', path: '/v1/bank-integrations/{id}', summary: 'Update the display name of a BankIntegration', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
- 'delete': { method: 'DELETE', path: '/v1/bank-integrations/{id}', summary: 'Delete a BankIntegration', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
- 'log': { method: 'GET', path: '/v1/bank-integrations/{id}/log', summary: 'Get activity log for a bank integration', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
- 'list-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'List bank statement import runs', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
- 'trigger-account-statement-runs': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'Trigger a bank statement import', pathParams: ['bankIntegrationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
- 'get-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}', summary: 'Get import run details', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
- 'account-statement-runs-files': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/files', summary: 'Get files for an import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
- 'trigger-account-statement-runs-reimport': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/reimport', summary: 'Trigger reimport of an existing account statement import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['fileIds'] },
26
- 'list-statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements', summary: 'List CAMT.053 statements for a bank integration', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
- 'statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}', summary: 'Get CAMT.053 statement detail', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
28
- 'download-statements-xml': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}/xml', summary: 'Get raw CAMT.053 XML for a statement', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false, isBinaryResponse: true, bodyArrayFields: [] },
11
+ 'initiate': { method: 'POST', path: '/v1/bank-integrations/initiate', summary: 'Initiate the OAuth/auth flow for a bank integration', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'providers-supported': { method: 'GET', path: '/v1/bank-integrations/providers/supported', summary: 'List supported bLink providers', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'providers-consent-status': { method: 'GET', path: '/v1/bank-integrations/providers/consent-status', summary: 'Get bLink consent status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'delete-providers-consent': { method: 'DELETE', path: '/v1/bank-integrations/providers/{providerId}/consent', summary: 'Revoke bLink consent for a provider', pathParams: ['providerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/bank-integrations', summary: 'List all BankIntegrations for the current tenant with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
+ 'create': { method: 'POST', path: '/v1/bank-integrations', summary: 'Create a BankIntegration record', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['methods'] },
17
+ 'get': { method: 'GET', path: '/v1/bank-integrations/{id}', summary: 'Get a BankIntegration by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/bank-integrations/{id}', summary: 'Update the display name of a BankIntegration', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/bank-integrations/{id}', summary: 'Delete a BankIntegration', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
+ 'log': { method: 'GET', path: '/v1/bank-integrations/{id}/log', summary: 'Get activity log for a bank integration', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
+ 'list-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'List bank statement import runs', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
+ 'trigger-account-statement-runs': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'Trigger a bank statement import', pathParams: ['bankIntegrationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
+ 'get-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}', summary: 'Get import run details', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
+ 'account-statement-runs-files': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/files', summary: 'Get files for an import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
+ 'trigger-account-statement-runs-reimport': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/reimport', summary: 'Trigger reimport of an existing account statement import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['fileIds'] },
26
+ 'account-statement-runs-files-download': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/files/{fileId}/download', summary: 'Download the stored CAMT XML for a statement file', pathParams: ['bankIntegrationId', 'importRunId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
27
+ 'list-all': { method: 'GET', path: '/v1/account-statement-files', summary: 'List statement import files across this tenant’s bank integrations', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
+ 'list-statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements', summary: 'List CAMT.053 statements for a bank integration', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
+ 'statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}', summary: 'Get CAMT.053 statement detail', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
+ 'download-statements-xml': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}/xml', summary: 'Get raw CAMT.053 XML for a statement', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
29
31
  };
30
32
  export default class BankIntegrations extends Command {
31
33
  static description = 'Manage bank integrations';
@@ -50,6 +52,7 @@ export default class BankIntegrations extends Command {
50
52
  file: Flags.string({ char: 'f', description: 'Read request body from file' }),
51
53
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
52
54
  'import-run-id': Flags.string({ description: 'Path param: importRunId' }),
55
+ 'file-id': Flags.string({ description: 'Path param: fileId' }),
53
56
  'statement-id': Flags.string({ description: 'Path param: statementId' }),
54
57
  page: Flags.string({ description: 'Page number' }),
55
58
  limit: Flags.string({ description: 'Items per page' }),
@@ -58,7 +61,7 @@ export default class BankIntegrations extends Command {
58
61
  '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
62
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
60
63
  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)' }),
64
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
62
65
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
63
66
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
64
67
  };
@@ -130,6 +133,8 @@ export default class BankIntegrations extends Command {
130
133
  queryParams.search = flags.search;
131
134
  if (flags['sort-by'])
132
135
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
136
+ if (flags.fields)
137
+ queryParams.fields = flags.fields;
133
138
  if (flags.query) {
134
139
  try {
135
140
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -277,10 +282,11 @@ export default class BankIntegrations extends Command {
277
282
  process.stderr.write('\n');
278
283
  }
279
284
  try {
280
- // Binary response: download to file
285
+ // Binary response: to -f / --file, or to stdout when it is redirected.
281
286
  if (endpoint.isBinaryResponse) {
282
- if (!flags.file) {
283
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
287
+ const toStdout = !flags.file;
288
+ if (toStdout && process.stdout.isTTY) {
289
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
284
290
  }
285
291
  const binResponse = await apiRequest(endpoint.method, apiPath, {
286
292
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -288,24 +294,55 @@ export default class BankIntegrations extends Command {
288
294
  contextName: flags.context,
289
295
  binary: true,
290
296
  });
291
- const fsModule = await import('node:fs');
292
297
  const binData = binResponse.data;
293
298
  const buffer = Buffer.isBuffer(binData)
294
299
  ? binData
295
300
  : Buffer.from(binData);
301
+ if (toStdout) {
302
+ // Nothing else may reach stdout here — it is the file.
303
+ process.stdout.write(buffer);
304
+ return;
305
+ }
306
+ const fsModule = await import('node:fs');
296
307
  fsModule.writeFileSync(flags.file, buffer);
297
308
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
298
309
  return;
299
310
  }
300
- const response = await apiRequest(endpoint.method, apiPath, {
301
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
302
- data,
303
- contextName: flags.context,
304
- acceptAny: true,
305
- });
311
+ let response;
312
+ if (endpoint.hasBinaryVariant) {
313
+ // This route answers JSON or a file depending on the request body, so the
314
+ // bytes are read first and the response's own content-type decides. Asking
315
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
316
+ const raw = await apiRequest(endpoint.method, apiPath, {
317
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
318
+ data,
319
+ contextName: flags.context,
320
+ binary: true,
321
+ });
322
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
323
+ const rawData = raw.data;
324
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
325
+ if (!isTextualMediaType(rawType)) {
326
+ if (process.stdout.isTTY) {
327
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
328
+ }
329
+ // Nothing else may reach stdout here — it is the file.
330
+ process.stdout.write(buffer);
331
+ return;
332
+ }
333
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
334
+ }
335
+ else {
336
+ response = await apiRequest(endpoint.method, apiPath, {
337
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
338
+ data,
339
+ contextName: flags.context,
340
+ acceptAny: true,
341
+ });
342
+ }
306
343
  // Check content-type for non-JSON responses (XML, text, etc.)
307
344
  const contentType = (response.headers['content-type'] || '').toLowerCase();
308
- if (contentType.includes('xml') || contentType.includes('text/')) {
345
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
309
346
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
310
347
  this.log(String(response.data));
311
348
  return;
@@ -332,11 +369,6 @@ export default class BankIntegrations extends Command {
332
369
  output = obj[arrayKeys[0]];
333
370
  }
334
371
  }
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
372
  // Sanitize output if requested
341
373
  if (flags.sanitize) {
342
374
  output = this.sanitizeOutput(output);
@@ -591,6 +623,8 @@ export default class BankIntegrations extends Command {
591
623
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
592
624
  if (flags.include)
593
625
  baseParams.include = flags.include;
626
+ if (flags.fields)
627
+ baseParams.fields = flags.fields;
594
628
  if (flags.query) {
595
629
  try {
596
630
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -645,12 +679,7 @@ export default class BankIntegrations extends Command {
645
679
  }
646
680
  }
647
681
  process.stderr.write(' done\n');
648
- // Filter fields if specified
649
682
  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
683
  // Sanitize output if requested
655
684
  if (flags.sanitize) {
656
685
  output = this.sanitizeOutput(output);
@@ -979,56 +1008,6 @@ export default class BankIntegrations extends Command {
979
1008
  return `\$${op}`;
980
1009
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
981
1010
  }
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
1011
  sanitizeOutput(data) {
1033
1012
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1034
1013
  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;