@aderis-erp/cli 1.1.4 → 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 (85) hide show
  1. package/dist/commands/access-groups.js +42 -8
  2. package/dist/commands/account-transaction-allocations.js +38 -4
  3. package/dist/commands/account-transactions.d.ts +2 -0
  4. package/dist/commands/account-transactions.js +68 -32
  5. package/dist/commands/accounts.js +46 -12
  6. package/dist/commands/agent-platform.js +37 -3
  7. package/dist/commands/agent-runs.js +49 -15
  8. package/dist/commands/analytics.js +39 -5
  9. package/dist/commands/api-clients.js +47 -13
  10. package/dist/commands/api-keys.js +40 -6
  11. package/dist/commands/async-jobs.js +45 -11
  12. package/dist/commands/audit-log.js +38 -4
  13. package/dist/commands/bank-accounts.js +54 -20
  14. package/dist/commands/bank-integrations.js +56 -22
  15. package/dist/commands/business-partners.js +77 -43
  16. package/dist/commands/chat.js +43 -9
  17. package/dist/commands/credit-card-accounts.js +57 -23
  18. package/dist/commands/currencies.js +37 -3
  19. package/dist/commands/custody-accounts.js +41 -7
  20. package/dist/commands/dashboards.js +52 -18
  21. package/dist/commands/delivery-notes.js +69 -35
  22. package/dist/commands/document-checksums.d.ts +1 -0
  23. package/dist/commands/document-checksums.js +38 -3
  24. package/dist/commands/dunning-configs.js +41 -7
  25. package/dist/commands/dunning-reminders.js +44 -10
  26. package/dist/commands/dunning-runs.js +37 -3
  27. package/dist/commands/email-transactions.js +39 -5
  28. package/dist/commands/employees.js +77 -43
  29. package/dist/commands/entity-configurations.js +39 -5
  30. package/dist/commands/event-mapping-rules.js +37 -3
  31. package/dist/commands/event-processor-mappings.js +41 -7
  32. package/dist/commands/files.js +48 -14
  33. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  34. package/dist/commands/financial-instrument-transactions.js +1369 -0
  35. package/dist/commands/financial-instrument-types.js +41 -7
  36. package/dist/commands/financial-instruments.js +41 -7
  37. package/dist/commands/health.js +41 -7
  38. package/dist/commands/imports.js +39 -5
  39. package/dist/commands/inbox.js +42 -8
  40. package/dist/commands/integrations.js +39 -5
  41. package/dist/commands/item-categories.js +41 -7
  42. package/dist/commands/items.js +42 -8
  43. package/dist/commands/journal-lines.js +37 -3
  44. package/dist/commands/journals.js +61 -27
  45. package/dist/commands/kpis.js +44 -10
  46. package/dist/commands/ledgers.js +83 -49
  47. package/dist/commands/my-account.js +54 -20
  48. package/dist/commands/notifications.js +37 -3
  49. package/dist/commands/office-locations.js +41 -7
  50. package/dist/commands/payment-methods.js +46 -12
  51. package/dist/commands/payment-runs.js +54 -20
  52. package/dist/commands/payment-terms.js +42 -8
  53. package/dist/commands/payments.js +43 -9
  54. package/dist/commands/payroll-runs.js +71 -37
  55. package/dist/commands/permissions.js +37 -3
  56. package/dist/commands/price-lists.js +48 -14
  57. package/dist/commands/processor-mappings.js +40 -6
  58. package/dist/commands/processors.js +46 -12
  59. package/dist/commands/purchase-order-types.js +41 -7
  60. package/dist/commands/purchase-orders.js +60 -26
  61. package/dist/commands/reports.js +43 -8
  62. package/dist/commands/roles.js +40 -6
  63. package/dist/commands/salary-types.js +41 -7
  64. package/dist/commands/sales-invoices.js +85 -51
  65. package/dist/commands/sales-orders.js +69 -35
  66. package/dist/commands/saved-views.js +39 -5
  67. package/dist/commands/scheduled-processor-mappings.js +41 -7
  68. package/dist/commands/schedules.js +45 -11
  69. package/dist/commands/settings.js +38 -4
  70. package/dist/commands/tags.js +42 -8
  71. package/dist/commands/tax-codes.js +44 -10
  72. package/dist/commands/tax-matrix.js +41 -7
  73. package/dist/commands/templates.js +49 -15
  74. package/dist/commands/tenants.js +44 -10
  75. package/dist/commands/tools.js +38 -4
  76. package/dist/commands/users.js +58 -24
  77. package/dist/commands/vat-declarations.js +61 -27
  78. package/dist/commands/vendor-invoices.js +81 -47
  79. package/dist/commands/webhooks.js +43 -9
  80. package/dist/commands/work-summary.js +37 -3
  81. package/dist/lib/paths.d.ts +8 -0
  82. package/dist/lib/paths.js +16 -0
  83. package/dist/lib/production-guard.js +2 -0
  84. package/oclif.manifest.json +199 -2
  85. 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,47 +12,47 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = ["notes", "files", "contacts", "contacts-notes", "beneficiary-accounts"];
12
13
  const SUBCOMMANDS = [...["list", "create", "match-candidates", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes", "list-beneficiary-accounts", "create-beneficiary-accounts", "get-beneficiary-accounts", "update-beneficiary-accounts", "delete-beneficiary-accounts", "beneficiary-accounts-approve", "beneficiary-accounts-retire", "beneficiary-accounts-reactivate", "beneficiary-accounts-default"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/business-partners', summary: 'Get all business partners with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
16
- 'match-candidates': { method: 'GET', path: '/v1/business-partners/match-candidates', summary: 'Rank the business partners whose names resemble a bank counterparty text', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'get': { method: 'GET', path: '/v1/business-partners/{id}', summary: 'Get a business partner by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
19
- 'delete': { method: 'DELETE', path: '/v1/business-partners/{id}', summary: 'Delete a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'merge': { method: 'POST', path: '/v1/business-partners/{id}/merge', summary: 'Merge a business partner into another', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'activate': { method: 'POST', path: '/v1/business-partners/{id}/activate', summary: 'Activate a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'get-favicon': { method: 'GET', path: '/v1/business-partners/{id}/favicon', summary: 'Download a business partner\'s stored favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'update-favicon': { method: 'PUT', path: '/v1/business-partners/{id}/favicon', summary: 'Manually upload a business partner\'s favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'trigger-favicon-backfill': { method: 'POST', path: '/v1/business-partners/favicon-backfill', summary: 'Fetch favicons for every business partner in this tenant that has none yet', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'list-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Get all notes for a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'create-notes': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Create a note for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
27
- 'get-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Get a specific note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'update-notes': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Update a note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'delete-notes': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Delete a note from a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'list-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files', summary: 'List file attachments for a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
31
- 'create-files': { method: 'POST', path: '/v1/business-partners/{parentId}/files', summary: 'Upload a file attachment to a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
32
- 'get-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Get a file attachment for a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
33
- 'update-files': { method: 'PATCH', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a business partner', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'delete-files': { method: 'DELETE', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
35
- 'files-download': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'list-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Get all contacts for a business partner with pagination', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'create-contacts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Create a new contact for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
38
- 'get-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Get a contact by ID', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
39
- 'update-contacts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Update a contact', pathParams: ['id', 'businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
40
- 'delete-contacts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Delete a contact', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
- 'list-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Get all notes for a contact', pathParams: ['contactId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
- 'create-contacts-notes': { method: 'POST', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Create a note for a contact', pathParams: ['contactId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
- 'get-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Get a specific note for a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
44
- 'update-contacts-notes': { method: 'PATCH', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Update a note for a contact', pathParams: ['contactId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
45
- 'delete-contacts-notes': { method: 'DELETE', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Delete a note from a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
46
- 'list-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'List the beneficiary bank accounts of a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
47
- 'create-beneficiary-accounts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'Store a new beneficiary bank account (status NEW)', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
48
- 'get-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Get one beneficiary bank account of a business partner', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
49
- 'update-beneficiary-accounts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Edit a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
50
- 'delete-beneficiary-accounts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Delete a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
51
- 'beneficiary-accounts-approve': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/approve', summary: 'Approve a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
52
- 'beneficiary-accounts-retire': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/retire', summary: 'Retire a beneficiary bank account; clears its default flag', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
53
- 'beneficiary-accounts-reactivate': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/reactivate', summary: 'Reactivate a retired account: NEW again, both approvals cleared', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
54
- 'beneficiary-accounts-default': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/default', summary: 'Make this account the partner default; the previous one is unset', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/business-partners', summary: 'Get all business partners with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'], bodyMediaType: '', specParams: [] },
17
+ 'match-candidates': { method: 'GET', path: '/v1/business-partners/match-candidates', summary: 'Rank the business partners whose names resemble a bank counterparty text', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'get': { method: 'GET', path: '/v1/business-partners/{id}', summary: 'Get a business partner by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'], bodyMediaType: '', specParams: [] },
20
+ 'delete': { method: 'DELETE', path: '/v1/business-partners/{id}', summary: 'Delete a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'merge': { method: 'POST', path: '/v1/business-partners/{id}/merge', summary: 'Merge a business partner into another', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'activate': { method: 'POST', path: '/v1/business-partners/{id}/activate', summary: 'Activate a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'get-favicon': { method: 'GET', path: '/v1/business-partners/{id}/favicon', summary: 'Download a business partner\'s stored favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'update-favicon': { method: 'PUT', path: '/v1/business-partners/{id}/favicon', summary: 'Manually upload a business partner\'s favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'trigger-favicon-backfill': { method: 'POST', path: '/v1/business-partners/favicon-backfill', summary: 'Fetch favicons for every business partner in this tenant that has none yet', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'list-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Get all notes for a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'create-notes': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Create a note for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
28
+ 'get-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Get a specific note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'update-notes': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Update a note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'delete-notes': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Delete a note from a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'list-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files', summary: 'List file attachments for a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
32
+ 'create-files': { method: 'POST', path: '/v1/business-partners/{parentId}/files', summary: 'Upload a file attachment to a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
33
+ 'get-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Get a file attachment for a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
34
+ 'update-files': { method: 'PATCH', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a business partner', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
+ 'delete-files': { method: 'DELETE', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
36
+ 'files-download': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'list-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Get all contacts for a business partner with pagination', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'create-contacts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Create a new contact for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'], bodyMediaType: '', specParams: [] },
39
+ 'get-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Get a contact by ID', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
40
+ 'update-contacts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Update a contact', pathParams: ['id', 'businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'], bodyMediaType: '', specParams: [] },
41
+ 'delete-contacts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Delete a contact', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
42
+ 'list-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Get all notes for a contact', pathParams: ['contactId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
43
+ 'create-contacts-notes': { method: 'POST', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Create a note for a contact', pathParams: ['contactId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
+ 'get-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Get a specific note for a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
45
+ 'update-contacts-notes': { method: 'PATCH', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Update a note for a contact', pathParams: ['contactId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
46
+ 'delete-contacts-notes': { method: 'DELETE', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Delete a note from a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
47
+ 'list-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'List the beneficiary bank accounts of a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
48
+ 'create-beneficiary-accounts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'Store a new beneficiary bank account (status NEW)', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
49
+ 'get-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Get one beneficiary bank account of a business partner', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
50
+ 'update-beneficiary-accounts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Edit a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
51
+ 'delete-beneficiary-accounts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Delete a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
52
+ 'beneficiary-accounts-approve': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/approve', summary: 'Approve a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
53
+ 'beneficiary-accounts-retire': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/retire', summary: 'Retire a beneficiary bank account; clears its default flag', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
54
+ 'beneficiary-accounts-reactivate': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/reactivate', summary: 'Reactivate a retired account: NEW again, both approvals cleared', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
55
+ 'beneficiary-accounts-default': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/default', summary: 'Make this account the partner default; the previous one is unset', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
55
56
  };
56
57
  export default class BusinessPartners extends Command {
57
58
  static description = 'Manage business partners';
@@ -73,7 +74,7 @@ export default class BusinessPartners extends Command {
73
74
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
74
75
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
75
76
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
76
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
77
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
77
78
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
78
79
  'note-id': Flags.string({ description: 'Path param: noteId' }),
79
80
  'file-id': Flags.string({ description: 'Path param: fileId' }),
@@ -208,6 +209,17 @@ export default class BusinessPartners extends Command {
208
209
  }
209
210
  }
210
211
  }
212
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
213
+ // have them: their body is not JSON, so --set cannot carry a value there.
214
+ for (const param of endpoint.specParams) {
215
+ const value = flags[param.flag];
216
+ if (value === undefined || value === '') {
217
+ if (param.required)
218
+ this.error(`Missing required flag: --${param.flag}`);
219
+ continue;
220
+ }
221
+ queryParams[param.name] = value;
222
+ }
211
223
  let data;
212
224
  if (endpoint.isMultipart) {
213
225
  if (!flags.file)
@@ -228,6 +240,25 @@ export default class BusinessPartners extends Command {
228
240
  }
229
241
  data = form;
230
242
  }
243
+ else if (endpoint.bodyMediaType) {
244
+ // The body is not JSON (today only application/x-ndjson): it is sent
245
+ // verbatim, under the content type the endpoint asks for.
246
+ if (flags.file) {
247
+ const fsModule = await import('node:fs');
248
+ try {
249
+ data = fsModule.readFileSync(flags.file, 'utf-8');
250
+ }
251
+ catch {
252
+ this.error(`Failed to read file: ${flags.file}`);
253
+ }
254
+ }
255
+ else if (flags.data) {
256
+ data = flags.data;
257
+ }
258
+ else {
259
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
260
+ }
261
+ }
231
262
  else if (endpoint.hasBody) {
232
263
  // For binary-response endpoints, --file is the output path, not a JSON
233
264
  // input file — skip the read-as-body branch.
@@ -323,7 +354,7 @@ export default class BusinessPartners extends Command {
323
354
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
324
355
  }
325
356
  else if (data) {
326
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
357
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
327
358
  }
328
359
  process.stderr.write('\n');
329
360
  }
@@ -337,6 +368,7 @@ export default class BusinessPartners extends Command {
337
368
  const binResponse = await apiRequest(endpoint.method, apiPath, {
338
369
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
339
370
  data,
371
+ contentType: endpoint.bodyMediaType || undefined,
340
372
  contextName: flags.context,
341
373
  binary: true,
342
374
  });
@@ -362,6 +394,7 @@ export default class BusinessPartners extends Command {
362
394
  const raw = await apiRequest(endpoint.method, apiPath, {
363
395
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
364
396
  data,
397
+ contentType: endpoint.bodyMediaType || undefined,
365
398
  contextName: flags.context,
366
399
  binary: true,
367
400
  });
@@ -382,6 +415,7 @@ export default class BusinessPartners extends Command {
382
415
  response = await apiRequest(endpoint.method, apiPath, {
383
416
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
384
417
  data,
418
+ contentType: endpoint.bodyMediaType || undefined,
385
419
  contextName: flags.context,
386
420
  acceptAny: true,
387
421
  });
@@ -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,13 +12,13 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["create", "get", "get-all", "update", "delete", "stream", "token-refresh"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'create': { method: 'POST', path: '/v1/chat', summary: 'Send a chat message and stream the assistant reply (SSE).', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['attachments'] },
15
- 'get': { method: 'GET', path: '/v1/conversations', summary: 'List the caller\'s chat conversations, most recently updated first.', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'get-all': { method: 'GET', path: '/v1/conversations/{id}', summary: 'Fetch one conversation with its messages.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/conversations/{id}', summary: 'Store client-owned UI state on a conversation.', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'delete': { method: 'DELETE', path: '/v1/conversations/{id}', summary: 'Delete one conversation.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'stream': { method: 'GET', path: '/v1/conversations/{id}/stream', summary: 'Reattach to a conversation\'s running turn (SSE replay + follow).', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'token-refresh': { method: 'POST', path: '/v1/chat/token/refresh', summary: 'Refresh the delegated token a chat turn runs on', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'create': { method: 'POST', path: '/v1/chat', summary: 'Send a chat message and stream the assistant reply (SSE).', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['attachments'], bodyMediaType: '', specParams: [] },
16
+ 'get': { method: 'GET', path: '/v1/conversations', summary: 'List the caller\'s chat conversations, most recently updated first.', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'get-all': { method: 'GET', path: '/v1/conversations/{id}', summary: 'Fetch one conversation with its messages.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/conversations/{id}', summary: 'Store client-owned UI state on a conversation.', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/conversations/{id}', summary: 'Delete one conversation.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'stream': { method: 'GET', path: '/v1/conversations/{id}/stream', summary: 'Reattach to a conversation\'s running turn (SSE replay + follow).', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'token-refresh': { method: 'POST', path: '/v1/chat/token/refresh', summary: 'Refresh the delegated token a chat turn runs on', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
22
  };
22
23
  export default class Chat extends Command {
23
24
  static description = 'Manage chat';
@@ -37,7 +38,7 @@ export default class Chat 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 Chat 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 Chat 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 Chat 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 Chat 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 Chat 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 Chat 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
  });
@@ -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,27 +12,27 @@ const SERVER_IMPORTS = ["import-cards"];
11
12
  const NESTED_UPSERTS = ["ledger-mappings", "notes"];
12
13
  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", "list-cards", "create-cards", "get-cards", "update-cards", "delete-cards", "import-cards"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- '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, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
16
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
18
- 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
20
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
21
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
22
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
23
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
24
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
25
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
26
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
27
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
28
- '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, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'list-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Get all cards on a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'create-cards': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Create a card on a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
31
- 'get-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Get a card by ID', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
32
- 'update-cards': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Update a card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
33
- 'delete-cards': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Delete a manual card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'import-cards': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/import', summary: 'Import provider cards into a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ '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, hasBinaryVariant: false, bodyArrayFields: ['notes'], bodyMediaType: '', specParams: [] },
17
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
28
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ '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, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'list-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Get all cards on a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'create-cards': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Create a card on a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
32
+ 'get-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Get a card by ID', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
33
+ 'update-cards': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Update a card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
34
+ 'delete-cards': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Delete a manual card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
+ 'import-cards': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/import', summary: 'Import provider cards into a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
36
  };
36
37
  export default class CreditCardAccounts extends Command {
37
38
  static description = 'Manage credit card accounts';
@@ -53,7 +54,7 @@ export default class CreditCardAccounts extends Command {
53
54
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
54
55
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
55
56
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
56
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
57
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
57
58
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
58
59
  mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
59
60
  reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
@@ -191,6 +192,17 @@ export default class CreditCardAccounts extends Command {
191
192
  }
192
193
  }
193
194
  }
195
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
196
+ // have them: their body is not JSON, so --set cannot carry a value there.
197
+ for (const param of endpoint.specParams) {
198
+ const value = flags[param.flag];
199
+ if (value === undefined || value === '') {
200
+ if (param.required)
201
+ this.error(`Missing required flag: --${param.flag}`);
202
+ continue;
203
+ }
204
+ queryParams[param.name] = value;
205
+ }
194
206
  let data;
195
207
  if (endpoint.isMultipart) {
196
208
  if (!flags.file)
@@ -211,6 +223,25 @@ export default class CreditCardAccounts extends Command {
211
223
  }
212
224
  data = form;
213
225
  }
226
+ else if (endpoint.bodyMediaType) {
227
+ // The body is not JSON (today only application/x-ndjson): it is sent
228
+ // verbatim, under the content type the endpoint asks for.
229
+ if (flags.file) {
230
+ const fsModule = await import('node:fs');
231
+ try {
232
+ data = fsModule.readFileSync(flags.file, 'utf-8');
233
+ }
234
+ catch {
235
+ this.error(`Failed to read file: ${flags.file}`);
236
+ }
237
+ }
238
+ else if (flags.data) {
239
+ data = flags.data;
240
+ }
241
+ else {
242
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
243
+ }
244
+ }
214
245
  else if (endpoint.hasBody) {
215
246
  // For binary-response endpoints, --file is the output path, not a JSON
216
247
  // input file — skip the read-as-body branch.
@@ -306,7 +337,7 @@ export default class CreditCardAccounts extends Command {
306
337
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
307
338
  }
308
339
  else if (data) {
309
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
340
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
310
341
  }
311
342
  process.stderr.write('\n');
312
343
  }
@@ -320,6 +351,7 @@ export default class CreditCardAccounts extends Command {
320
351
  const binResponse = await apiRequest(endpoint.method, apiPath, {
321
352
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
322
353
  data,
354
+ contentType: endpoint.bodyMediaType || undefined,
323
355
  contextName: flags.context,
324
356
  binary: true,
325
357
  });
@@ -345,6 +377,7 @@ export default class CreditCardAccounts extends Command {
345
377
  const raw = await apiRequest(endpoint.method, apiPath, {
346
378
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
347
379
  data,
380
+ contentType: endpoint.bodyMediaType || undefined,
348
381
  contextName: flags.context,
349
382
  binary: true,
350
383
  });
@@ -365,6 +398,7 @@ export default class CreditCardAccounts extends Command {
365
398
  response = await apiRequest(endpoint.method, apiPath, {
366
399
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
367
400
  data,
401
+ contentType: endpoint.bodyMediaType || undefined,
368
402
  contextName: flags.context,
369
403
  acceptAny: true,
370
404
  });