@aderis-erp/cli 1.1.3 → 1.1.5

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 (99) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/access-groups.js +42 -8
  3. package/dist/commands/account-transaction-allocations.js +38 -4
  4. package/dist/commands/account-transactions.d.ts +2 -0
  5. package/dist/commands/account-transactions.js +68 -32
  6. package/dist/commands/accounts.js +46 -12
  7. package/dist/commands/agent-platform.js +37 -3
  8. package/dist/commands/agent-runs.js +49 -15
  9. package/dist/commands/analytics.js +39 -5
  10. package/dist/commands/api-clients.d.ts +58 -0
  11. package/dist/commands/api-clients.js +1375 -0
  12. package/dist/commands/api-keys.js +40 -6
  13. package/dist/commands/async-jobs.js +45 -11
  14. package/dist/commands/audit-log.js +38 -4
  15. package/dist/commands/bank-accounts.js +54 -20
  16. package/dist/commands/bank-integrations.js +56 -22
  17. package/dist/commands/business-partners.js +77 -43
  18. package/dist/commands/chat.js +43 -9
  19. package/dist/commands/credit-card-accounts.d.ts +4 -0
  20. package/dist/commands/credit-card-accounts.js +64 -25
  21. package/dist/commands/currencies.d.ts +56 -0
  22. package/dist/commands/currencies.js +1361 -0
  23. package/dist/commands/custody-accounts.d.ts +56 -0
  24. package/dist/commands/custody-accounts.js +1369 -0
  25. package/dist/commands/{integration-identities.d.ts → dashboards.d.ts} +3 -3
  26. package/dist/commands/dashboards.js +1381 -0
  27. package/dist/commands/delivery-notes.js +69 -35
  28. package/dist/commands/document-checksums.d.ts +57 -0
  29. package/dist/commands/{integration-identities.js → document-checksums.js} +48 -21
  30. package/dist/commands/dunning-configs.js +41 -7
  31. package/dist/commands/dunning-reminders.js +44 -10
  32. package/dist/commands/dunning-runs.js +37 -3
  33. package/dist/commands/email-transactions.js +39 -5
  34. package/dist/commands/employees.js +77 -43
  35. package/dist/commands/entity-configurations.js +39 -5
  36. package/dist/commands/event-mapping-rules.js +37 -3
  37. package/dist/commands/event-processor-mappings.js +41 -7
  38. package/dist/commands/files.js +48 -14
  39. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  40. package/dist/commands/financial-instrument-transactions.js +1369 -0
  41. package/dist/commands/financial-instrument-types.d.ts +56 -0
  42. package/dist/commands/financial-instrument-types.js +1369 -0
  43. package/dist/commands/financial-instruments.d.ts +56 -0
  44. package/dist/commands/financial-instruments.js +1369 -0
  45. package/dist/commands/health.js +41 -7
  46. package/dist/commands/imports.js +39 -5
  47. package/dist/commands/inbox.js +42 -8
  48. package/dist/commands/integrations.js +39 -5
  49. package/dist/commands/item-categories.js +41 -7
  50. package/dist/commands/items.js +42 -8
  51. package/dist/commands/journal-lines.js +37 -3
  52. package/dist/commands/journals.js +61 -27
  53. package/dist/commands/{workflows.d.ts → kpis.d.ts} +1 -1
  54. package/dist/commands/kpis.js +1371 -0
  55. package/dist/commands/ledgers.js +83 -49
  56. package/dist/commands/my-account.js +54 -20
  57. package/dist/commands/notifications.d.ts +56 -0
  58. package/dist/commands/notifications.js +1360 -0
  59. package/dist/commands/office-locations.js +41 -7
  60. package/dist/commands/payment-methods.js +46 -12
  61. package/dist/commands/payment-runs.js +54 -20
  62. package/dist/commands/payment-terms.js +42 -8
  63. package/dist/commands/payments.js +43 -9
  64. package/dist/commands/payroll-runs.js +71 -37
  65. package/dist/commands/permissions.js +37 -3
  66. package/dist/commands/price-lists.js +48 -14
  67. package/dist/commands/processor-mappings.js +40 -6
  68. package/dist/commands/processors.js +46 -12
  69. package/dist/commands/purchase-order-types.js +41 -7
  70. package/dist/commands/purchase-orders.js +60 -26
  71. package/dist/commands/reports.js +43 -8
  72. package/dist/commands/roles.js +40 -6
  73. package/dist/commands/salary-types.js +41 -7
  74. package/dist/commands/sales-invoices.js +85 -51
  75. package/dist/commands/sales-orders.js +69 -35
  76. package/dist/commands/saved-views.js +39 -5
  77. package/dist/commands/scheduled-processor-mappings.js +41 -7
  78. package/dist/commands/schedules.js +45 -11
  79. package/dist/commands/settings.d.ts +56 -0
  80. package/dist/commands/settings.js +1361 -0
  81. package/dist/commands/tags.js +42 -8
  82. package/dist/commands/tax-codes.js +44 -10
  83. package/dist/commands/tax-matrix.js +41 -7
  84. package/dist/commands/templates.js +50 -15
  85. package/dist/commands/tenants.js +44 -10
  86. package/dist/commands/tools.js +38 -4
  87. package/dist/commands/users.js +58 -24
  88. package/dist/commands/vat-declarations.js +61 -27
  89. package/dist/commands/vendor-invoices.js +82 -47
  90. package/dist/commands/webhooks.js +43 -9
  91. package/dist/commands/work-summary.d.ts +56 -0
  92. package/dist/commands/{workflows.js → work-summary.js} +49 -18
  93. package/dist/lib/auth/index.js +5 -0
  94. package/dist/lib/paths.d.ts +8 -0
  95. package/dist/lib/paths.js +16 -0
  96. package/dist/lib/production-guard.d.ts +42 -0
  97. package/dist/lib/production-guard.js +94 -0
  98. package/oclif.manifest.json +2142 -333
  99. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,39 +12,39 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = ["line-items", "notes", "files"];
12
13
  const SUBCOMMANDS = [...["list", "create", "create-from-orders", "create-from-invoice", "create-invoice", "get", "update", "delete", "get-pdf", "create-pdf", "submit", "approve", "cancel", "replace", "copy", "deliver", "ship", "send", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/delivery-notes', summary: 'Get all delivery notes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'lineItems'] },
16
- 'create-from-orders': { method: 'POST', path: '/v1/delivery-notes/create-from-orders', summary: 'Create a DRAFT delivery note from several sales orders', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderIds'] },
17
- 'create-from-invoice': { method: 'POST', path: '/v1/delivery-notes/create-from-invoice', summary: 'Create a DRAFT delivery note from a sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'create-invoice': { method: 'POST', path: '/v1/delivery-notes/create-invoice', summary: 'Create ONE draft sales invoice from several delivery notes (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['deliveryNoteIds'] },
19
- 'get': { method: 'GET', path: '/v1/delivery-notes/{id}', summary: 'Get a delivery note by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'removeFileIds'] },
21
- 'delete': { method: 'DELETE', path: '/v1/delivery-notes/{id}', summary: 'Delete a delivery note', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'get-pdf': { method: 'GET', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF as the mapped event would, or preview a stored template', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'create-pdf': { method: 'POST', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF from unsaved HTML posted in the body', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'submit': { method: 'POST', path: '/v1/delivery-notes/{id}/submit', summary: 'Submit a delivery note for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'approve': { method: 'POST', path: '/v1/delivery-notes/{id}/approve', summary: 'Approve a delivery note (DRAFT or PENDING_APPROVAL → APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'cancel': { method: 'POST', path: '/v1/delivery-notes/{id}/cancel', summary: 'Cancel a delivery note (DRAFT / PENDING_APPROVAL / APPROVED / SHIPPED / DELIVERED → CANCELLED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
27
- 'replace': { method: 'POST', path: '/v1/delivery-notes/{id}/replace', summary: 'Replace a delivery note (APPROVED / SHIPPED / DELIVERED / CANCELLED → REPLACED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'copy': { method: 'POST', path: '/v1/delivery-notes/{id}/copy', summary: 'Create a copy of a delivery note as a new draft', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'deliver': { method: 'POST', path: '/v1/delivery-notes/{id}/deliver', summary: 'Mark a delivery note as delivered (SHIPPED → DELIVERED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'ship': { method: 'POST', path: '/v1/delivery-notes/{id}/ship', summary: 'Ship a delivery note (APPROVED → SHIPPED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lineItems'] },
31
- 'send': { method: 'POST', path: '/v1/delivery-notes/{id}/send', summary: 'Send a delivery note to its recipient by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['emailCc', 'emailBcc'] },
32
- 'create-line-items': { method: 'POST', path: '/v1/delivery-notes/{noteId}/line-items', summary: 'Add a line item to a delivery note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'] },
33
- 'update-line-items-reorder': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['items'] },
34
- 'update-line-items': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Update a line item on a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'] },
35
- 'delete-line-items': { method: 'DELETE', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Remove a line item from a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'list-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Get all notes for a delivery note', pathParams: ['deliveryNoteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'create-notes': { method: 'POST', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Create a note for a delivery note', pathParams: ['deliveryNoteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
38
- 'get-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Get a specific note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
39
- 'update-notes': { method: 'PATCH', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Update a note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
40
- 'delete-notes': { method: 'DELETE', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Delete a note from a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
- 'list-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files', summary: 'List file attachments for a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
- 'create-files': { method: 'POST', path: '/v1/delivery-notes/{parentId}/files', summary: 'Upload a file attachment to a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
- 'get-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Get a file attachment for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
44
- 'update-files': { method: 'PATCH', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
45
- 'delete-files': { method: 'DELETE', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
46
- 'files-download': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/delivery-notes', summary: 'Get all delivery notes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'lineItems'], bodyMediaType: '', specParams: [] },
17
+ 'create-from-orders': { method: 'POST', path: '/v1/delivery-notes/create-from-orders', summary: 'Create a DRAFT delivery note from several sales orders', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderIds'], bodyMediaType: '', specParams: [] },
18
+ 'create-from-invoice': { method: 'POST', path: '/v1/delivery-notes/create-from-invoice', summary: 'Create a DRAFT delivery note from a sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'create-invoice': { method: 'POST', path: '/v1/delivery-notes/create-invoice', summary: 'Create ONE draft sales invoice from several delivery notes (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['deliveryNoteIds'], bodyMediaType: '', specParams: [] },
20
+ 'get': { method: 'GET', path: '/v1/delivery-notes/{id}', summary: 'Get a delivery note by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'removeFileIds'], bodyMediaType: '', specParams: [] },
22
+ 'delete': { method: 'DELETE', path: '/v1/delivery-notes/{id}', summary: 'Delete a delivery note', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'get-pdf': { method: 'GET', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF as the mapped event would, or preview a stored template', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'create-pdf': { method: 'POST', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF from unsaved HTML posted in the body', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'submit': { method: 'POST', path: '/v1/delivery-notes/{id}/submit', summary: 'Submit a delivery note for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'approve': { method: 'POST', path: '/v1/delivery-notes/{id}/approve', summary: 'Approve a delivery note (DRAFT or PENDING_APPROVAL → APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'cancel': { method: 'POST', path: '/v1/delivery-notes/{id}/cancel', summary: 'Cancel a delivery note (DRAFT / PENDING_APPROVAL / APPROVED / SHIPPED / DELIVERED → CANCELLED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
28
+ 'replace': { method: 'POST', path: '/v1/delivery-notes/{id}/replace', summary: 'Replace a delivery note (APPROVED / SHIPPED / DELIVERED / CANCELLED → REPLACED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'copy': { method: 'POST', path: '/v1/delivery-notes/{id}/copy', summary: 'Create a copy of a delivery note as a new draft', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'deliver': { method: 'POST', path: '/v1/delivery-notes/{id}/deliver', summary: 'Mark a delivery note as delivered (SHIPPED → DELIVERED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'ship': { method: 'POST', path: '/v1/delivery-notes/{id}/ship', summary: 'Ship a delivery note (APPROVED → SHIPPED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lineItems'], bodyMediaType: '', specParams: [] },
32
+ 'send': { method: 'POST', path: '/v1/delivery-notes/{id}/send', summary: 'Send a delivery note to its recipient by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['emailCc', 'emailBcc'], bodyMediaType: '', specParams: [] },
33
+ 'create-line-items': { method: 'POST', path: '/v1/delivery-notes/{noteId}/line-items', summary: 'Add a line item to a delivery note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
34
+ 'update-line-items-reorder': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['items'], bodyMediaType: '', specParams: [] },
35
+ 'update-line-items': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Update a line item on a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
36
+ 'delete-line-items': { method: 'DELETE', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Remove a line item from a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'list-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Get all notes for a delivery note', pathParams: ['deliveryNoteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'create-notes': { method: 'POST', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Create a note for a delivery note', pathParams: ['deliveryNoteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
+ 'get-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Get a specific note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
40
+ 'update-notes': { method: 'PATCH', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Update a note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
41
+ 'delete-notes': { method: 'DELETE', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Delete a note from a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
42
+ 'list-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files', summary: 'List file attachments for a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
43
+ 'create-files': { method: 'POST', path: '/v1/delivery-notes/{parentId}/files', summary: 'Upload a file attachment to a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
+ 'get-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Get a file attachment for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
45
+ 'update-files': { method: 'PATCH', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
46
+ 'delete-files': { method: 'DELETE', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
47
+ 'files-download': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
47
48
  };
48
49
  export default class DeliveryNotes extends Command {
49
50
  static description = 'Manage delivery notes';
@@ -65,7 +66,7 @@ export default class DeliveryNotes extends Command {
65
66
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
66
67
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
67
68
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
68
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
69
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
69
70
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
70
71
  'line-item-id': Flags.string({ description: 'Path param: lineItemId' }),
71
72
  'note-id': Flags.string({ description: 'Path param: noteId' }),
@@ -199,6 +200,17 @@ export default class DeliveryNotes extends Command {
199
200
  }
200
201
  }
201
202
  }
203
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
204
+ // have them: their body is not JSON, so --set cannot carry a value there.
205
+ for (const param of endpoint.specParams) {
206
+ const value = flags[param.flag];
207
+ if (value === undefined || value === '') {
208
+ if (param.required)
209
+ this.error(`Missing required flag: --${param.flag}`);
210
+ continue;
211
+ }
212
+ queryParams[param.name] = value;
213
+ }
202
214
  let data;
203
215
  if (endpoint.isMultipart) {
204
216
  if (!flags.file)
@@ -219,6 +231,25 @@ export default class DeliveryNotes extends Command {
219
231
  }
220
232
  data = form;
221
233
  }
234
+ else if (endpoint.bodyMediaType) {
235
+ // The body is not JSON (today only application/x-ndjson): it is sent
236
+ // verbatim, under the content type the endpoint asks for.
237
+ if (flags.file) {
238
+ const fsModule = await import('node:fs');
239
+ try {
240
+ data = fsModule.readFileSync(flags.file, 'utf-8');
241
+ }
242
+ catch {
243
+ this.error(`Failed to read file: ${flags.file}`);
244
+ }
245
+ }
246
+ else if (flags.data) {
247
+ data = flags.data;
248
+ }
249
+ else {
250
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
251
+ }
252
+ }
222
253
  else if (endpoint.hasBody) {
223
254
  // For binary-response endpoints, --file is the output path, not a JSON
224
255
  // input file — skip the read-as-body branch.
@@ -314,7 +345,7 @@ export default class DeliveryNotes extends Command {
314
345
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
315
346
  }
316
347
  else if (data) {
317
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
348
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
318
349
  }
319
350
  process.stderr.write('\n');
320
351
  }
@@ -328,6 +359,7 @@ export default class DeliveryNotes extends Command {
328
359
  const binResponse = await apiRequest(endpoint.method, apiPath, {
329
360
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
330
361
  data,
362
+ contentType: endpoint.bodyMediaType || undefined,
331
363
  contextName: flags.context,
332
364
  binary: true,
333
365
  });
@@ -353,6 +385,7 @@ export default class DeliveryNotes extends Command {
353
385
  const raw = await apiRequest(endpoint.method, apiPath, {
354
386
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
355
387
  data,
388
+ contentType: endpoint.bodyMediaType || undefined,
356
389
  contextName: flags.context,
357
390
  binary: true,
358
391
  });
@@ -373,6 +406,7 @@ export default class DeliveryNotes extends Command {
373
406
  response = await apiRequest(endpoint.method, apiPath, {
374
407
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
375
408
  data,
409
+ contentType: endpoint.bodyMediaType || undefined,
376
410
  contextName: flags.context,
377
411
  acceptAny: true,
378
412
  });
@@ -0,0 +1,57 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DocumentChecksums extends Command {
3
+ static description: string;
4
+ static examples: never[];
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
+ percentage: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
+ };
30
+ run(): Promise<void>;
31
+ private runUpsert;
32
+ /**
33
+ * The API's own import: the whole array goes up in one request and comes
34
+ * back as an import run, which records what happened to every record. A bad
35
+ * record lands in errorLog and the rest are still applied, and the run stays
36
+ * readable afterwards with: aderis imports get <id>.
37
+ */
38
+ private runServerImport;
39
+ /** Renders an import run: its counts, then whatever the server refused. */
40
+ private printImportRun;
41
+ private runExport;
42
+ private runCamt053Import;
43
+ private runNestedUpsert;
44
+ private parseFilterValue;
45
+ /** The page line under a table: which page, how many, and any sums the API returned. */
46
+ private printPageSummary;
47
+ /**
48
+ * The API caps how many records a page may hold. Asking for more and getting
49
+ * fewer looks like "that is everything" unless it is said out loud.
50
+ */
51
+ private warnShortPage;
52
+ private sanitizeOutput;
53
+ private getColumns;
54
+ private getValue;
55
+ private printTable;
56
+ private printCsv;
57
+ }
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -9,19 +10,12 @@ const HAS_EXPORT = false;
9
10
  const HAS_CAMT053 = false;
10
11
  const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
- const SUBCOMMANDS = [...["list-integrations", "create-integrations", "integrations-disable-all", "get-integrations", "list-integrations-secrets", "create-integrations-secrets", "delete-integrations-secrets", "integrations-disable"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
+ const SUBCOMMANDS = [...["validate"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list-integrations': { method: 'GET', path: '/v1/auth/integrations', summary: 'List current tenant integrations, including disabled identities', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create-integrations': { method: 'POST', path: '/v1/auth/integrations', summary: 'Create an integration in the current tenant', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['permissions'] },
16
- 'integrations-disable-all': { method: 'POST', path: '/v1/auth/integrations/disable-all', summary: 'Disable current tenant integrations', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'get-integrations': { method: 'GET', path: '/v1/auth/integrations/{integrationId}', summary: 'Read one current tenant integration', pathParams: ['integrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'list-integrations-secrets': { method: 'GET', path: '/v1/auth/integrations/{integrationId}/secrets', summary: 'List integration secret metadata', pathParams: ['integrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'create-integrations-secrets': { method: 'POST', path: '/v1/auth/integrations/{integrationId}/secrets', summary: 'Issue an integration secret', pathParams: ['integrationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'delete-integrations-secrets': { method: 'DELETE', path: '/v1/auth/integrations/{integrationId}/secrets/{secretId}', summary: 'Revoke one integration secret', pathParams: ['integrationId', 'secretId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'integrations-disable': { method: 'POST', path: '/v1/auth/integrations/{integrationId}/disable', summary: 'Disable an integration and end its sessions', pathParams: ['integrationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'validate': { method: 'POST', path: '/v1/document-checksums/validate', summary: 'Validate documents against a checksum log', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: 'application/x-ndjson', specParams: [{ "flag": "percentage", "name": "percentage", "required": true }] },
22
16
  };
23
- export default class IntegrationIdentities extends Command {
24
- static description = 'Manage integration identities';
17
+ export default class DocumentChecksums extends Command {
18
+ static description = 'Manage document checksums';
25
19
  static examples = [];
26
20
  static args = {
27
21
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -33,9 +27,9 @@ export default class IntegrationIdentities extends Command {
33
27
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
34
28
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
35
29
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
36
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
30
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
37
31
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
38
- 'secret-id': Flags.string({ description: 'Path param: secretId' }),
32
+ 'percentage': Flags.string({ description: 'Required. Share of the documents in the log to check, in percent: more than 0, at most 100. The run checks ⌈documents in the log × percentage ÷ 100⌉ documents, at least one, drawn at random with every docume...' }),
39
33
  page: Flags.string({ description: 'Page number' }),
40
34
  limit: Flags.string({ description: 'Items per page' }),
41
35
  include: Flags.string({ description: 'Relations to include' }),
@@ -49,14 +43,14 @@ export default class IntegrationIdentities extends Command {
49
43
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
50
44
  };
51
45
  async run() {
52
- const { args, flags } = await this.parse(IntegrationIdentities);
46
+ const { args, flags } = await this.parse(DocumentChecksums);
53
47
  // Bulk actions. 'import' is the API's own endpoint: one request, answered
54
48
  // by a run that records what happened to every record. 'upsert' is the CLI
55
49
  // looping create/update. A resource has one or the other, never both.
56
50
  if (args.action === 'upsert') {
57
51
  if (!HAS_UPSERT) {
58
52
  this.error(SERVER_IMPORTS.includes('import')
59
- ? 'This resource is imported by the API. Use: aderis integration-identities import --file data.json'
53
+ ? 'This resource is imported by the API. Use: aderis document-checksums import --file data.json'
60
54
  : 'Upsert needs both a create and an update endpoint, which this resource does not have');
61
55
  }
62
56
  await this.runUpsert(flags);
@@ -66,7 +60,7 @@ export default class IntegrationIdentities extends Command {
66
60
  const suffix = args.action.replace('upsert-', '');
67
61
  if (!NESTED_UPSERTS.includes(suffix)) {
68
62
  this.error(SERVER_IMPORTS.includes('import-' + suffix)
69
- ? `That resource is imported by the API. Use: aderis integration-identities import-${suffix} <id> --file data.json`
63
+ ? `That resource is imported by the API. Use: aderis document-checksums import-${suffix} <id> --file data.json`
70
64
  : `Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
71
65
  }
72
66
  await this.runNestedUpsert(suffix, args.id, flags);
@@ -96,7 +90,7 @@ export default class IntegrationIdentities extends Command {
96
90
  const suffix = args.action === 'import' ? '' : '-' + args.action.replace('import-', '');
97
91
  const loops = args.action === 'import' ? HAS_UPSERT : NESTED_UPSERTS.includes(args.action.replace('import-', ''));
98
92
  if (loops) {
99
- this.error(`This resource has no import endpoint. Use: aderis integration-identities upsert${suffix}${suffix ? ' <id>' : ''} --file data.json`);
93
+ this.error(`This resource has no import endpoint. Use: aderis document-checksums upsert${suffix}${suffix ? ' <id>' : ''} --file data.json`);
100
94
  }
101
95
  }
102
96
  const endpoint = ENDPOINTS[args.action];
@@ -110,7 +104,7 @@ export default class IntegrationIdentities extends Command {
110
104
  // First path param always comes from positional argument
111
105
  value = args.id;
112
106
  if (!value)
113
- this.error('Missing required argument: id. Usage: aderis integration-identities <action> <id>');
107
+ this.error('Missing required argument: id. Usage: aderis document-checksums <action> <id>');
114
108
  }
115
109
  else {
116
110
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -165,6 +159,17 @@ export default class IntegrationIdentities extends Command {
165
159
  }
166
160
  }
167
161
  }
162
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
163
+ // have them: their body is not JSON, so --set cannot carry a value there.
164
+ for (const param of endpoint.specParams) {
165
+ const value = flags[param.flag];
166
+ if (value === undefined || value === '') {
167
+ if (param.required)
168
+ this.error(`Missing required flag: --${param.flag}`);
169
+ continue;
170
+ }
171
+ queryParams[param.name] = value;
172
+ }
168
173
  let data;
169
174
  if (endpoint.isMultipart) {
170
175
  if (!flags.file)
@@ -185,6 +190,25 @@ export default class IntegrationIdentities extends Command {
185
190
  }
186
191
  data = form;
187
192
  }
193
+ else if (endpoint.bodyMediaType) {
194
+ // The body is not JSON (today only application/x-ndjson): it is sent
195
+ // verbatim, under the content type the endpoint asks for.
196
+ if (flags.file) {
197
+ const fsModule = await import('node:fs');
198
+ try {
199
+ data = fsModule.readFileSync(flags.file, 'utf-8');
200
+ }
201
+ catch {
202
+ this.error(`Failed to read file: ${flags.file}`);
203
+ }
204
+ }
205
+ else if (flags.data) {
206
+ data = flags.data;
207
+ }
208
+ else {
209
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
210
+ }
211
+ }
188
212
  else if (endpoint.hasBody) {
189
213
  // For binary-response endpoints, --file is the output path, not a JSON
190
214
  // input file — skip the read-as-body branch.
@@ -280,7 +304,7 @@ export default class IntegrationIdentities extends Command {
280
304
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
281
305
  }
282
306
  else if (data) {
283
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
307
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
284
308
  }
285
309
  process.stderr.write('\n');
286
310
  }
@@ -294,6 +318,7 @@ export default class IntegrationIdentities extends Command {
294
318
  const binResponse = await apiRequest(endpoint.method, apiPath, {
295
319
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
296
320
  data,
321
+ contentType: endpoint.bodyMediaType || undefined,
297
322
  contextName: flags.context,
298
323
  binary: true,
299
324
  });
@@ -319,6 +344,7 @@ export default class IntegrationIdentities extends Command {
319
344
  const raw = await apiRequest(endpoint.method, apiPath, {
320
345
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
321
346
  data,
347
+ contentType: endpoint.bodyMediaType || undefined,
322
348
  contextName: flags.context,
323
349
  binary: true,
324
350
  });
@@ -339,6 +365,7 @@ export default class IntegrationIdentities extends Command {
339
365
  response = await apiRequest(endpoint.method, apiPath, {
340
366
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
341
367
  data,
368
+ contentType: endpoint.bodyMediaType || undefined,
342
369
  contextName: flags.context,
343
370
  acceptAny: true,
344
371
  });
@@ -641,7 +668,7 @@ export default class IntegrationIdentities extends Command {
641
668
  let apiPath = endpoint.path;
642
669
  if (endpoint.pathParams.length > 0) {
643
670
  if (!parentId)
644
- this.error(`Missing required argument: id. Usage: aderis integration-identities ${action} <parent-id> --file data.json`);
671
+ this.error(`Missing required argument: id. Usage: aderis document-checksums ${action} <parent-id> --file data.json`);
645
672
  apiPath = apiPath.replace(`{${endpoint.pathParams[0]}}`, parentId);
646
673
  }
647
674
  // The endpoint takes an array. A single object is wrapped, so importing
@@ -914,7 +941,7 @@ export default class IntegrationIdentities extends Command {
914
941
  }
915
942
  async runNestedUpsert(suffix, parentId, flags) {
916
943
  if (!parentId) {
917
- this.error(`Missing required argument: id. Usage: aderis integration-identities upsert-${suffix} <parent-id> --file data.json`);
944
+ this.error(`Missing required argument: id. Usage: aderis document-checksums upsert-${suffix} <parent-id> --file data.json`);
918
945
  }
919
946
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
920
947
  if (!context)
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,11 +12,11 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
16
- 'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
18
- 'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
20
  };
20
21
  export default class DunningConfigs extends Command {
21
22
  static description = 'Manage dunning configs';
@@ -37,7 +38,7 @@ export default class DunningConfigs extends Command {
37
38
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
38
39
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
39
40
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
40
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
41
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
41
42
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
42
43
  page: Flags.string({ description: 'Page number' }),
43
44
  limit: Flags.string({ description: 'Items per page' }),
@@ -168,6 +169,17 @@ export default class DunningConfigs extends Command {
168
169
  }
169
170
  }
170
171
  }
172
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
173
+ // have them: their body is not JSON, so --set cannot carry a value there.
174
+ for (const param of endpoint.specParams) {
175
+ const value = flags[param.flag];
176
+ if (value === undefined || value === '') {
177
+ if (param.required)
178
+ this.error(`Missing required flag: --${param.flag}`);
179
+ continue;
180
+ }
181
+ queryParams[param.name] = value;
182
+ }
171
183
  let data;
172
184
  if (endpoint.isMultipart) {
173
185
  if (!flags.file)
@@ -188,6 +200,25 @@ export default class DunningConfigs extends Command {
188
200
  }
189
201
  data = form;
190
202
  }
203
+ else if (endpoint.bodyMediaType) {
204
+ // The body is not JSON (today only application/x-ndjson): it is sent
205
+ // verbatim, under the content type the endpoint asks for.
206
+ if (flags.file) {
207
+ const fsModule = await import('node:fs');
208
+ try {
209
+ data = fsModule.readFileSync(flags.file, 'utf-8');
210
+ }
211
+ catch {
212
+ this.error(`Failed to read file: ${flags.file}`);
213
+ }
214
+ }
215
+ else if (flags.data) {
216
+ data = flags.data;
217
+ }
218
+ else {
219
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
220
+ }
221
+ }
191
222
  else if (endpoint.hasBody) {
192
223
  // For binary-response endpoints, --file is the output path, not a JSON
193
224
  // input file — skip the read-as-body branch.
@@ -283,7 +314,7 @@ export default class DunningConfigs extends Command {
283
314
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
284
315
  }
285
316
  else if (data) {
286
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
317
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
287
318
  }
288
319
  process.stderr.write('\n');
289
320
  }
@@ -297,6 +328,7 @@ export default class DunningConfigs extends Command {
297
328
  const binResponse = await apiRequest(endpoint.method, apiPath, {
298
329
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
299
330
  data,
331
+ contentType: endpoint.bodyMediaType || undefined,
300
332
  contextName: flags.context,
301
333
  binary: true,
302
334
  });
@@ -322,6 +354,7 @@ export default class DunningConfigs extends Command {
322
354
  const raw = await apiRequest(endpoint.method, apiPath, {
323
355
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
324
356
  data,
357
+ contentType: endpoint.bodyMediaType || undefined,
325
358
  contextName: flags.context,
326
359
  binary: true,
327
360
  });
@@ -342,6 +375,7 @@ export default class DunningConfigs extends Command {
342
375
  response = await apiRequest(endpoint.method, apiPath, {
343
376
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
344
377
  data,
378
+ contentType: endpoint.bodyMediaType || undefined,
345
379
  contextName: flags.context,
346
380
  acceptAny: true,
347
381
  });