@aderis-erp/cli 1.0.2 → 1.0.4

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 (191) hide show
  1. package/README.md +6 -0
  2. package/dist/commands/access-groups 3.d.ts +40 -0
  3. package/dist/commands/{access-groups 2.js → access-groups 3.js } +106 -40
  4. package/dist/commands/access-groups.d.ts +40 -0
  5. package/dist/commands/access-groups.js +1074 -0
  6. package/dist/commands/account-transactions.d.ts +42 -0
  7. package/dist/commands/account-transactions.js +1088 -0
  8. package/dist/commands/accounts 2.d.ts +41 -0
  9. package/dist/commands/accounts 2.js +1078 -0
  10. package/dist/commands/accounts.d.ts +41 -0
  11. package/dist/commands/accounts.js +1080 -0
  12. package/dist/commands/agent-runs.d.ts +40 -0
  13. package/dist/commands/agent-runs.js +1071 -0
  14. package/dist/commands/async-endpoints-internal 3.d.ts +40 -0
  15. package/dist/commands/{processing-logs.js → async-endpoints-internal 3.js } +110 -46
  16. package/dist/commands/async-endpoints-internal.d.ts +40 -0
  17. package/dist/commands/async-endpoints-internal.js +1064 -0
  18. package/dist/commands/audit-log 3.d.ts +40 -0
  19. package/dist/commands/audit-log 3.js +1066 -0
  20. package/dist/commands/audit-log.d.ts +40 -0
  21. package/dist/commands/audit-log.js +1068 -0
  22. package/dist/commands/auth/login.d.ts +14 -0
  23. package/dist/commands/auth/login.js +116 -0
  24. package/dist/commands/auth/logout.d.ts +10 -0
  25. package/dist/commands/auth/logout.js +58 -0
  26. package/dist/commands/auth/status.d.ts +12 -0
  27. package/dist/commands/auth/status.js +101 -0
  28. package/dist/commands/auth/tenant.d.ts +19 -0
  29. package/dist/commands/auth/tenant.js +184 -0
  30. package/dist/commands/auth.d.ts +37 -0
  31. package/dist/commands/auth.js +871 -0
  32. package/dist/commands/bank-accounts.d.ts +42 -0
  33. package/dist/commands/bank-accounts.js +1088 -0
  34. package/dist/commands/bank-integrations.d.ts +1 -0
  35. package/dist/commands/bank-integrations.js +122 -54
  36. package/dist/commands/business-partners 3.d.ts +41 -0
  37. package/dist/commands/business-partners 3.js +1078 -0
  38. package/dist/commands/business-partners---contacts 3.d.ts +42 -0
  39. package/dist/commands/business-partners---contacts 3.js +1072 -0
  40. package/dist/commands/business-partners---contacts.d.ts +1 -0
  41. package/dist/commands/business-partners---contacts.js +114 -46
  42. package/dist/commands/business-partners.d.ts +1 -0
  43. package/dist/commands/business-partners.js +114 -46
  44. package/dist/commands/config/add.d.ts +13 -0
  45. package/dist/commands/config/add.js +120 -0
  46. package/dist/commands/config/current.d.ts +9 -0
  47. package/dist/commands/config/current.js +43 -0
  48. package/dist/commands/config/delete.d.ts +12 -0
  49. package/dist/commands/config/delete.js +67 -0
  50. package/dist/commands/config/list.d.ts +9 -0
  51. package/dist/commands/config/list.js +50 -0
  52. package/dist/commands/config/use.d.ts +9 -0
  53. package/dist/commands/config/use.js +40 -0
  54. package/dist/commands/credit-card-accounts 3.d.ts +42 -0
  55. package/dist/commands/credit-card-accounts 3.js +1084 -0
  56. package/dist/commands/credit-card-accounts.d.ts +1 -0
  57. package/dist/commands/credit-card-accounts.js +119 -51
  58. package/dist/commands/documents.d.ts +1 -0
  59. package/dist/commands/documents.js +110 -42
  60. package/dist/commands/dunning-configs 3.d.ts +40 -0
  61. package/dist/commands/dunning-configs 3.js +1072 -0
  62. package/dist/commands/dunning-configs.d.ts +1 -0
  63. package/dist/commands/dunning-configs.js +109 -41
  64. package/dist/commands/dunning-reminders.d.ts +1 -0
  65. package/dist/commands/dunning-reminders.js +112 -44
  66. package/dist/commands/dunning-runs 3.d.ts +40 -0
  67. package/dist/commands/{pubsub.js → dunning-runs 3.js } +108 -43
  68. package/dist/commands/dunning-runs.d.ts +1 -0
  69. package/dist/commands/dunning-runs.js +105 -37
  70. package/dist/commands/employees.d.ts +1 -0
  71. package/dist/commands/employees.js +136 -68
  72. package/dist/commands/event-processor-mappings 3.d.ts +40 -0
  73. package/dist/commands/event-processor-mappings 3.js +1071 -0
  74. package/dist/commands/event-processor-mappings.d.ts +1 -0
  75. package/dist/commands/event-processor-mappings.js +109 -41
  76. package/dist/commands/files-internal 3.d.ts +40 -0
  77. package/dist/commands/files-internal 3.js +1073 -0
  78. package/dist/commands/files-internal.d.ts +1 -0
  79. package/dist/commands/files-internal.js +113 -45
  80. package/dist/commands/health 3.d.ts +40 -0
  81. package/dist/commands/health 3.js +1067 -0
  82. package/dist/commands/health.d.ts +1 -0
  83. package/dist/commands/health.js +109 -41
  84. package/dist/commands/{pubsub.d.ts → inbox 3.d.ts } +2 -1
  85. package/dist/commands/inbox 3.js +1065 -0
  86. package/dist/commands/inbox.d.ts +1 -0
  87. package/dist/commands/inbox.js +110 -40
  88. package/dist/commands/integrations 3.d.ts +41 -0
  89. package/dist/commands/integrations 3.js +1067 -0
  90. package/dist/commands/integrations.d.ts +1 -0
  91. package/dist/commands/integrations.js +107 -39
  92. package/dist/commands/item-categories 3.d.ts +40 -0
  93. package/dist/commands/item-categories 3.js +1072 -0
  94. package/dist/commands/item-categories.d.ts +1 -0
  95. package/dist/commands/item-categories.js +109 -41
  96. package/dist/commands/items 3.d.ts +40 -0
  97. package/dist/commands/items 3.js +1072 -0
  98. package/dist/commands/items.d.ts +1 -0
  99. package/dist/commands/items.js +109 -41
  100. package/dist/commands/journal-lines.d.ts +1 -0
  101. package/dist/commands/journal-lines.js +105 -37
  102. package/dist/commands/journals 3.d.ts +42 -0
  103. package/dist/commands/journals 3.js +1088 -0
  104. package/dist/commands/journals.d.ts +1 -0
  105. package/dist/commands/journals.js +123 -55
  106. package/dist/commands/ledgers.d.ts +1 -0
  107. package/dist/commands/ledgers.js +120 -52
  108. package/dist/commands/office-locations 3.d.ts +40 -0
  109. package/dist/commands/office-locations 3.js +1072 -0
  110. package/dist/commands/office-locations.d.ts +1 -0
  111. package/dist/commands/office-locations.js +109 -41
  112. package/dist/commands/payment-method-ledger-mappings 3.d.ts +41 -0
  113. package/dist/commands/payment-method-ledger-mappings 3.js +1066 -0
  114. package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
  115. package/dist/commands/payment-method-ledger-mappings.js +109 -41
  116. package/dist/commands/payment-methods 3.d.ts +40 -0
  117. package/dist/commands/payment-methods 3.js +1072 -0
  118. package/dist/commands/payment-methods.d.ts +1 -0
  119. package/dist/commands/payment-methods.js +109 -41
  120. package/dist/commands/payment-runs 3.d.ts +42 -0
  121. package/dist/commands/payment-runs 3.js +1087 -0
  122. package/dist/commands/payment-runs.d.ts +1 -0
  123. package/dist/commands/payment-runs.js +122 -54
  124. package/dist/commands/payment-terms 3.d.ts +40 -0
  125. package/dist/commands/payment-terms 3.js +1073 -0
  126. package/dist/commands/payment-terms.d.ts +1 -0
  127. package/dist/commands/payment-terms.js +110 -42
  128. package/dist/commands/price-lists 3.d.ts +41 -0
  129. package/dist/commands/price-lists 3.js +1078 -0
  130. package/dist/commands/price-lists.d.ts +1 -0
  131. package/dist/commands/price-lists.js +114 -46
  132. package/dist/commands/processor-mappings 2.d.ts +40 -0
  133. package/dist/commands/processor-mappings 2.js +1070 -0
  134. package/dist/commands/processor-mappings.d.ts +1 -0
  135. package/dist/commands/processor-mappings.js +108 -40
  136. package/dist/commands/processors.d.ts +1 -0
  137. package/dist/commands/processors.js +113 -45
  138. package/dist/commands/reconciliation 3.d.ts +40 -0
  139. package/dist/commands/reconciliation 3.js +1069 -0
  140. package/dist/commands/reconciliation.d.ts +1 -0
  141. package/dist/commands/reconciliation.js +110 -42
  142. package/dist/commands/reports.d.ts +1 -0
  143. package/dist/commands/reports.js +110 -39
  144. package/dist/commands/salary-types 3.d.ts +40 -0
  145. package/dist/commands/salary-types 3.js +1072 -0
  146. package/dist/commands/salary-types.d.ts +1 -0
  147. package/dist/commands/salary-types.js +1074 -0
  148. package/dist/commands/sales-invoices 3.d.ts +43 -0
  149. package/dist/commands/sales-invoices 3.js +1100 -0
  150. package/dist/commands/sales-invoices.d.ts +1 -0
  151. package/dist/commands/sales-invoices.js +134 -66
  152. package/dist/commands/sales-orders 3.d.ts +43 -0
  153. package/dist/commands/sales-orders 3.js +1098 -0
  154. package/dist/commands/sales-orders.d.ts +1 -0
  155. package/dist/commands/sales-orders.js +133 -64
  156. package/dist/commands/switch.d.ts +1 -0
  157. package/dist/commands/switch.js +58 -3
  158. package/dist/commands/{processing-logs.d.ts → tags 3.d.ts } +2 -1
  159. package/dist/commands/tags 3.js +1072 -0
  160. package/dist/commands/tags.d.ts +1 -0
  161. package/dist/commands/tags.js +109 -41
  162. package/dist/commands/tax-codes 3.d.ts +40 -0
  163. package/dist/commands/tax-codes 3.js +1075 -0
  164. package/dist/commands/tax-codes.d.ts +1 -0
  165. package/dist/commands/tax-codes.js +112 -44
  166. package/dist/commands/tax-matrix.d.ts +1 -0
  167. package/dist/commands/tax-matrix.js +109 -41
  168. package/dist/commands/tenants.d.ts +1 -0
  169. package/dist/commands/tenants.js +109 -41
  170. package/dist/commands/text-templates 3.d.ts +40 -0
  171. package/dist/commands/text-templates 3.js +1072 -0
  172. package/dist/commands/text-templates.d.ts +1 -0
  173. package/dist/commands/text-templates.js +109 -41
  174. package/dist/commands/tools.d.ts +1 -0
  175. package/dist/commands/tools.js +106 -38
  176. package/dist/commands/vendor-invoices.d.ts +1 -0
  177. package/dist/commands/vendor-invoices.js +139 -69
  178. package/dist/commands/webhooks 3.d.ts +40 -0
  179. package/dist/commands/webhooks 3.js +1074 -0
  180. package/dist/commands/webhooks.d.ts +1 -0
  181. package/dist/commands/webhooks.js +111 -43
  182. package/dist/commands/workflows.d.ts +1 -0
  183. package/dist/commands/workflows.js +107 -39
  184. package/dist/lib/api-client.d.ts +5 -1
  185. package/dist/lib/api-client.js +19 -5
  186. package/dist/lib/auth.d.ts +16 -1
  187. package/dist/lib/auth.js +170 -45
  188. package/dist/lib/config.d.ts +7 -0
  189. package/dist/lib/config.js +7 -1
  190. package/oclif.manifest.json +7464 -653
  191. package/package.json +2 -2
@@ -32,6 +32,7 @@ export default class CreditCardAccounts extends Command {
32
32
  private runExport;
33
33
  private runCamt053Import;
34
34
  private runNestedImport;
35
+ private parseFilterValue;
35
36
  private filterFields;
36
37
  private sanitizeOutput;
37
38
  private getColumns;
@@ -8,21 +8,21 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["ledger-mappings", "notes"];
9
9
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "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}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
14
- 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false },
15
- 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false },
16
- 'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false },
17
- 'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false },
18
- 'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false },
19
- 'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false },
20
- 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false },
21
- 'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false },
22
- 'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false },
23
- 'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false },
24
- 'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false },
25
- 'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['last4', 'notes'] },
13
+ 'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['last4', 'notes'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
+ 'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
+ 'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
+ 'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
+ 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
+ 'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
+ 'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
+ 'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
+ 'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
+ 'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
26
  };
27
27
  export default class CreditCardAccounts extends Command {
28
28
  static description = 'Manage credit card accounts';
@@ -142,15 +142,7 @@ export default class CreditCardAccounts extends Command {
142
142
  if (eqIndex === -1)
143
143
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
144
144
  const key = f.slice(0, eqIndex);
145
- let value = f.slice(eqIndex + 1);
146
- // Check for operator prefix (with or without $)
147
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
148
- if (opMatch) {
149
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
150
- }
151
- else {
152
- value = `\$eq:${value}`;
153
- }
145
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
154
146
  const filterKey = `filter.${key}`;
155
147
  if (filterKey in queryParams) {
156
148
  // Multiple filters on same field: collect as array
@@ -183,7 +175,9 @@ export default class CreditCardAccounts extends Command {
183
175
  data = form;
184
176
  }
185
177
  else if (endpoint.hasBody) {
186
- if (flags.file) {
178
+ // For binary-response endpoints, --file is the output path, not a JSON
179
+ // input file — skip the read-as-body branch.
180
+ if (flags.file && !endpoint.isBinaryResponse) {
187
181
  const fsModule = await import('node:fs');
188
182
  try {
189
183
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -205,6 +199,7 @@ export default class CreditCardAccounts extends Command {
205
199
  if (!data)
206
200
  data = {};
207
201
  const body = data;
202
+ const arrayFields = new Set(endpoint.bodyArrayFields);
208
203
  for (const pair of flags.set) {
209
204
  const eqIndex = pair.indexOf('=');
210
205
  if (eqIndex === -1)
@@ -216,6 +211,19 @@ export default class CreditCardAccounts extends Command {
216
211
  value = JSON.parse(value);
217
212
  }
218
213
  catch { /* keep as string */ }
214
+ // For top-level keys typed as array in the API schema, split bare
215
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
216
+ // wrap a lone scalar in a single-element array. Skips when --set
217
+ // already provided a JSON array.
218
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
219
+ if (isArrayField && !Array.isArray(value)) {
220
+ if (typeof value === 'string') {
221
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
222
+ }
223
+ else if (value !== undefined && value !== null) {
224
+ value = [value];
225
+ }
226
+ }
219
227
  // Support nested keys like "address.city"
220
228
  const keys = key.split('.');
221
229
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -266,6 +274,26 @@ export default class CreditCardAccounts extends Command {
266
274
  process.stderr.write('\n');
267
275
  }
268
276
  try {
277
+ // Binary response: download to file
278
+ if (endpoint.isBinaryResponse) {
279
+ if (!flags.file) {
280
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
281
+ }
282
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
283
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
284
+ data,
285
+ contextName: flags.context,
286
+ binary: true,
287
+ });
288
+ const fsModule = await import('node:fs');
289
+ const binData = binResponse.data;
290
+ const buffer = Buffer.isBuffer(binData)
291
+ ? binData
292
+ : Buffer.from(binData);
293
+ fsModule.writeFileSync(flags.file, buffer);
294
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
295
+ return;
296
+ }
269
297
  const response = await apiRequest(endpoint.method, apiPath, {
270
298
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
271
299
  data,
@@ -356,6 +384,7 @@ export default class CreditCardAccounts extends Command {
356
384
  }
357
385
  else if (flags.set && flags.set.length > 0) {
358
386
  const item = {};
387
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
359
388
  for (const pair of flags.set) {
360
389
  const eqIndex = pair.indexOf('=');
361
390
  if (eqIndex === -1)
@@ -366,6 +395,15 @@ export default class CreditCardAccounts extends Command {
366
395
  value = JSON.parse(value);
367
396
  }
368
397
  catch { /* keep as string */ }
398
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
399
+ if (isArrayField && !Array.isArray(value)) {
400
+ if (typeof value === 'string') {
401
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
402
+ }
403
+ else if (value !== undefined && value !== null) {
404
+ value = [value];
405
+ }
406
+ }
369
407
  const keys = key.split('.');
370
408
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
371
409
  this.error(`Invalid --set key: ${key}`);
@@ -515,14 +553,7 @@ export default class CreditCardAccounts extends Command {
515
553
  if (eqIndex === -1)
516
554
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
517
555
  const key = f.slice(0, eqIndex);
518
- let value = f.slice(eqIndex + 1);
519
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
520
- if (opMatch) {
521
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
522
- }
523
- else {
524
- value = `\$eq:${value}`;
525
- }
556
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
526
557
  const filterKey = `filter.${key}`;
527
558
  if (filterKey in baseParams) {
528
559
  const existing = baseParams[filterKey];
@@ -699,6 +730,7 @@ export default class CreditCardAccounts extends Command {
699
730
  }
700
731
  else if (flags.set && flags.set.length > 0) {
701
732
  const item = {};
733
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
702
734
  for (const pair of flags.set) {
703
735
  const eqIndex = pair.indexOf('=');
704
736
  if (eqIndex === -1)
@@ -709,6 +741,15 @@ export default class CreditCardAccounts extends Command {
709
741
  value = JSON.parse(value);
710
742
  }
711
743
  catch { /* keep as string */ }
744
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
745
+ if (isArrayField && !Array.isArray(value)) {
746
+ if (typeof value === 'string') {
747
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
748
+ }
749
+ else if (value !== undefined && value !== null) {
750
+ value = [value];
751
+ }
752
+ }
712
753
  const keys = key.split('.');
713
754
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
714
755
  this.error(`Invalid --set key: ${key}`);
@@ -828,35 +869,62 @@ export default class CreditCardAccounts extends Command {
828
869
  this.log('');
829
870
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
830
871
  }
872
+ parseFilterValue(value) {
873
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
874
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
875
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
876
+ // no operator prefix is present.
877
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
878
+ const m = value.match(opRe);
879
+ if (!m)
880
+ return `\$eq:${value}`;
881
+ const op = m[1];
882
+ const rest = value.slice(m[0].length);
883
+ if (rest === '')
884
+ return `\$${op}`;
885
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
886
+ }
831
887
  filterFields(data, fields) {
832
888
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
833
889
  const pick = (obj, keys) => {
834
- const result = {};
890
+ // Group keys by their first segment so sibling sub-paths
891
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
892
+ // and don't clobber each other on merge.
893
+ const directKeys = new Set();
894
+ const nestedKeys = new Map();
835
895
  for (const key of keys) {
836
896
  if (key.split('.').some(isUnsafeKey))
837
897
  continue;
838
898
  if (key.includes('.')) {
839
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
840
899
  const [first, ...rest] = key.split('.');
841
- const val = obj[first];
842
- if (val && typeof val === 'object') {
843
- const restKey = rest.join('.');
844
- if (Array.isArray(val)) {
845
- // Map over array elements (e.g., allocations[].salesInvoiceId)
846
- const existing = Array.isArray(result[first]) ? result[first] : [];
847
- result[first] = val.map((item, i) => ({
848
- ...(existing[i] || {}),
849
- ...pick(item, [restKey]),
850
- }));
851
- }
852
- else {
853
- const nested = pick(val, [restKey]);
854
- result[first] = { ...(result[first] || {}), ...nested };
855
- }
900
+ if (isUnsafeKey(first))
901
+ continue;
902
+ const list = nestedKeys.get(first);
903
+ if (list) {
904
+ list.push(rest.join('.'));
905
+ }
906
+ else {
907
+ nestedKeys.set(first, [rest.join('.')]);
856
908
  }
857
909
  }
858
- else if (key in obj) {
910
+ else {
911
+ directKeys.add(key);
912
+ }
913
+ }
914
+ const result = {};
915
+ for (const key of directKeys) {
916
+ if (key in obj)
859
917
  result[key] = obj[key];
918
+ }
919
+ for (const [first, restKeys] of nestedKeys) {
920
+ const val = obj[first];
921
+ if (!val || typeof val !== 'object')
922
+ continue;
923
+ if (Array.isArray(val)) {
924
+ result[first] = val.map(item => pick(item, restKeys));
925
+ }
926
+ else {
927
+ result[first] = pick(val, restKeys);
860
928
  }
861
929
  }
862
930
  return result;
@@ -30,6 +30,7 @@ export default class Documents extends Command {
30
30
  private runExport;
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
+ private parseFilterValue;
33
34
  private filterFields;
34
35
  private sanitizeOutput;
35
36
  private getColumns;
@@ -8,12 +8,12 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/documents', summary: 'Create a new document (letterbox)', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false },
14
- 'update': { method: 'PATCH', path: '/v1/documents/{id}', summary: 'Patch an existing document', pathParams: ['id'], hasBody: true, isMultipart: false },
15
- 'delete': { method: 'DELETE', path: '/v1/documents/{id}', summary: 'Soft-delete (discard) a document', pathParams: ['id'], hasBody: true, isMultipart: false },
16
- 'reclassify': { method: 'POST', path: '/v1/documents/{id}/reclassify', summary: 'Reclassify a document to a new type', pathParams: ['id'], hasBody: true, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/documents', summary: 'Create a new document (letterbox)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['references', 'tags', 'files'] },
13
+ 'get': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/documents/{id}', summary: 'Patch an existing document', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['references', 'tags', 'files'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/documents/{id}', summary: 'Soft-delete (discard) a document', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'reclassify': { method: 'POST', path: '/v1/documents/{id}/reclassify', summary: 'Reclassify a document to a new type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
17
  };
18
18
  export default class Documents extends Command {
19
19
  static description = 'Manage documents';
@@ -131,15 +131,7 @@ export default class Documents extends Command {
131
131
  if (eqIndex === -1)
132
132
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
133
133
  const key = f.slice(0, eqIndex);
134
- let value = f.slice(eqIndex + 1);
135
- // Check for operator prefix (with or without $)
136
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
137
- if (opMatch) {
138
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
139
- }
140
- else {
141
- value = `\$eq:${value}`;
142
- }
134
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
143
135
  const filterKey = `filter.${key}`;
144
136
  if (filterKey in queryParams) {
145
137
  // Multiple filters on same field: collect as array
@@ -172,7 +164,9 @@ export default class Documents extends Command {
172
164
  data = form;
173
165
  }
174
166
  else if (endpoint.hasBody) {
175
- if (flags.file) {
167
+ // For binary-response endpoints, --file is the output path, not a JSON
168
+ // input file — skip the read-as-body branch.
169
+ if (flags.file && !endpoint.isBinaryResponse) {
176
170
  const fsModule = await import('node:fs');
177
171
  try {
178
172
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -194,6 +188,7 @@ export default class Documents extends Command {
194
188
  if (!data)
195
189
  data = {};
196
190
  const body = data;
191
+ const arrayFields = new Set(endpoint.bodyArrayFields);
197
192
  for (const pair of flags.set) {
198
193
  const eqIndex = pair.indexOf('=');
199
194
  if (eqIndex === -1)
@@ -205,6 +200,19 @@ export default class Documents extends Command {
205
200
  value = JSON.parse(value);
206
201
  }
207
202
  catch { /* keep as string */ }
203
+ // For top-level keys typed as array in the API schema, split bare
204
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
205
+ // wrap a lone scalar in a single-element array. Skips when --set
206
+ // already provided a JSON array.
207
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
208
+ if (isArrayField && !Array.isArray(value)) {
209
+ if (typeof value === 'string') {
210
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
211
+ }
212
+ else if (value !== undefined && value !== null) {
213
+ value = [value];
214
+ }
215
+ }
208
216
  // Support nested keys like "address.city"
209
217
  const keys = key.split('.');
210
218
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -255,6 +263,26 @@ export default class Documents extends Command {
255
263
  process.stderr.write('\n');
256
264
  }
257
265
  try {
266
+ // Binary response: download to file
267
+ if (endpoint.isBinaryResponse) {
268
+ if (!flags.file) {
269
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
270
+ }
271
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
272
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
273
+ data,
274
+ contextName: flags.context,
275
+ binary: true,
276
+ });
277
+ const fsModule = await import('node:fs');
278
+ const binData = binResponse.data;
279
+ const buffer = Buffer.isBuffer(binData)
280
+ ? binData
281
+ : Buffer.from(binData);
282
+ fsModule.writeFileSync(flags.file, buffer);
283
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
284
+ return;
285
+ }
258
286
  const response = await apiRequest(endpoint.method, apiPath, {
259
287
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
260
288
  data,
@@ -345,6 +373,7 @@ export default class Documents extends Command {
345
373
  }
346
374
  else if (flags.set && flags.set.length > 0) {
347
375
  const item = {};
376
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
348
377
  for (const pair of flags.set) {
349
378
  const eqIndex = pair.indexOf('=');
350
379
  if (eqIndex === -1)
@@ -355,6 +384,15 @@ export default class Documents extends Command {
355
384
  value = JSON.parse(value);
356
385
  }
357
386
  catch { /* keep as string */ }
387
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
388
+ if (isArrayField && !Array.isArray(value)) {
389
+ if (typeof value === 'string') {
390
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
391
+ }
392
+ else if (value !== undefined && value !== null) {
393
+ value = [value];
394
+ }
395
+ }
358
396
  const keys = key.split('.');
359
397
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
360
398
  this.error(`Invalid --set key: ${key}`);
@@ -504,14 +542,7 @@ export default class Documents extends Command {
504
542
  if (eqIndex === -1)
505
543
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
506
544
  const key = f.slice(0, eqIndex);
507
- let value = f.slice(eqIndex + 1);
508
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
509
- if (opMatch) {
510
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
511
- }
512
- else {
513
- value = `\$eq:${value}`;
514
- }
545
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
515
546
  const filterKey = `filter.${key}`;
516
547
  if (filterKey in baseParams) {
517
548
  const existing = baseParams[filterKey];
@@ -688,6 +719,7 @@ export default class Documents extends Command {
688
719
  }
689
720
  else if (flags.set && flags.set.length > 0) {
690
721
  const item = {};
722
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
691
723
  for (const pair of flags.set) {
692
724
  const eqIndex = pair.indexOf('=');
693
725
  if (eqIndex === -1)
@@ -698,6 +730,15 @@ export default class Documents extends Command {
698
730
  value = JSON.parse(value);
699
731
  }
700
732
  catch { /* keep as string */ }
733
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
734
+ if (isArrayField && !Array.isArray(value)) {
735
+ if (typeof value === 'string') {
736
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
737
+ }
738
+ else if (value !== undefined && value !== null) {
739
+ value = [value];
740
+ }
741
+ }
701
742
  const keys = key.split('.');
702
743
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
703
744
  this.error(`Invalid --set key: ${key}`);
@@ -817,35 +858,62 @@ export default class Documents extends Command {
817
858
  this.log('');
818
859
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
819
860
  }
861
+ parseFilterValue(value) {
862
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
863
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
864
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
865
+ // no operator prefix is present.
866
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
867
+ const m = value.match(opRe);
868
+ if (!m)
869
+ return `\$eq:${value}`;
870
+ const op = m[1];
871
+ const rest = value.slice(m[0].length);
872
+ if (rest === '')
873
+ return `\$${op}`;
874
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
875
+ }
820
876
  filterFields(data, fields) {
821
877
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
822
878
  const pick = (obj, keys) => {
823
- const result = {};
879
+ // Group keys by their first segment so sibling sub-paths
880
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
881
+ // and don't clobber each other on merge.
882
+ const directKeys = new Set();
883
+ const nestedKeys = new Map();
824
884
  for (const key of keys) {
825
885
  if (key.split('.').some(isUnsafeKey))
826
886
  continue;
827
887
  if (key.includes('.')) {
828
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
829
888
  const [first, ...rest] = key.split('.');
830
- const val = obj[first];
831
- if (val && typeof val === 'object') {
832
- const restKey = rest.join('.');
833
- if (Array.isArray(val)) {
834
- // Map over array elements (e.g., allocations[].salesInvoiceId)
835
- const existing = Array.isArray(result[first]) ? result[first] : [];
836
- result[first] = val.map((item, i) => ({
837
- ...(existing[i] || {}),
838
- ...pick(item, [restKey]),
839
- }));
840
- }
841
- else {
842
- const nested = pick(val, [restKey]);
843
- result[first] = { ...(result[first] || {}), ...nested };
844
- }
889
+ if (isUnsafeKey(first))
890
+ continue;
891
+ const list = nestedKeys.get(first);
892
+ if (list) {
893
+ list.push(rest.join('.'));
894
+ }
895
+ else {
896
+ nestedKeys.set(first, [rest.join('.')]);
845
897
  }
846
898
  }
847
- else if (key in obj) {
899
+ else {
900
+ directKeys.add(key);
901
+ }
902
+ }
903
+ const result = {};
904
+ for (const key of directKeys) {
905
+ if (key in obj)
848
906
  result[key] = obj[key];
907
+ }
908
+ for (const [first, restKeys] of nestedKeys) {
909
+ const val = obj[first];
910
+ if (!val || typeof val !== 'object')
911
+ continue;
912
+ if (Array.isArray(val)) {
913
+ result[first] = val.map(item => pick(item, restKeys));
914
+ }
915
+ else {
916
+ result[first] = pick(val, restKeys);
849
917
  }
850
918
  }
851
919
  return result;
@@ -0,0 +1,40 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DunningConfigs extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ };
28
+ run(): Promise<void>;
29
+ private runImport;
30
+ private runExport;
31
+ private runCamt053Import;
32
+ private runNestedImport;
33
+ private parseFilterValue;
34
+ private filterFields;
35
+ private sanitizeOutput;
36
+ private getColumns;
37
+ private getValue;
38
+ private printTable;
39
+ private printCsv;
40
+ }