@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,51 +12,51 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = ["line-items", "notes", "files", "payments"];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "payment-derivation", "beneficiary-check", "submit", "approve", "decline", "book", "rebuild-journal", "cancel", "replace", "change-vendor", "recalculate-payment-status", "validate", "create-line-items", "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", "list-payments", "create-payments", "get-payments", "update-payments", "delete-payments", "payments-approve", "payments-reopen", "payments-cancel", "create-offsets", "delete-offsets", "offset-candidates", "create-nettings", "delete-nettings", "netting-candidates"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/vendor-invoices', summary: 'Get all vendor invoices with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/vendor-invoices', summary: 'Create a new vendor invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'payments'] },
16
- 'get': { method: 'GET', path: '/v1/vendor-invoices/{id}', summary: 'Get a vendor invoice by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/vendor-invoices/{id}', summary: 'Update a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
18
- 'delete': { method: 'DELETE', path: '/v1/vendor-invoices/{id}', summary: 'Delete a vendor invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'payment-derivation': { method: 'GET', path: '/v1/vendor-invoices/{id}/payment-derivation', summary: 'Explain how a vendor invoice\'s paid amount is derived', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'beneficiary-check': { method: 'GET', path: '/v1/vendor-invoices/{id}/beneficiary-check', summary: 'Check a vendor invoice\'s beneficiary account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'submit': { method: 'POST', path: '/v1/vendor-invoices/{id}/submit', summary: 'Submit a vendor invoice for approval (transitions DRAFT to PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'approve': { method: 'POST', path: '/v1/vendor-invoices/{id}/approve', summary: 'Approve a vendor invoice (transitions DRAFT/PENDING_APPROVAL to APPROVED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
23
- 'decline': { method: 'POST', path: '/v1/vendor-invoices/{id}/decline', summary: 'Decline a vendor invoice (transitions PENDING_APPROVAL to DECLINED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'book': { method: 'POST', path: '/v1/vendor-invoices/{id}/book', summary: 'Manually book a vendor invoice (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'rebuild-journal': { method: 'POST', path: '/v1/vendor-invoices/{id}/rebuild-journal', summary: 'Rebuild a vendor invoice booking journal (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'cancel': { method: 'POST', path: '/v1/vendor-invoices/{id}/cancel', summary: 'Cancel a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
27
- 'replace': { method: 'POST', path: '/v1/vendor-invoices/{id}/replace', summary: 'Replace a vendor invoice with a new draft', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'change-vendor': { method: 'POST', path: '/v1/vendor-invoices/{id}/change-vendor', summary: 'Change the vendor on a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'recalculate-payment-status': { method: 'POST', path: '/v1/vendor-invoices/{id}/recalculate-payment-status', summary: 'Recalculate a vendor invoice\'s open amount and payment status', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'validate': { method: 'POST', path: '/v1/vendor-invoices/{id}/validate', summary: 'Validate a vendor invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
31
- 'create-line-items': { method: 'POST', path: '/v1/vendor-invoices/{invoiceId}/line-items', summary: 'Add a line item to a vendor invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'purchaseOrderLineItems', 'creditedVendorInvoiceLineItems'] },
32
- 'update-line-items': { method: 'PATCH', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'purchaseOrderLineItems', 'creditedVendorInvoiceLineItems'] },
33
- 'delete-line-items': { method: 'DELETE', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Remove a line item from a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'list-notes': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes', summary: 'Get all notes for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
35
- 'create-notes': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes', summary: 'Create a note for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'get-notes': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Get a specific note for a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'update-notes': { method: 'PATCH', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Update a note for a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
38
- 'delete-notes': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Delete a note from a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
39
- 'list-files': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files', summary: 'List file attachments for a vendor invoice', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
40
- 'create-files': { method: 'POST', path: '/v1/vendor-invoices/{parentId}/files', summary: 'Upload a file attachment to a vendor invoice', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
- 'get-files': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Get a file attachment for a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
- 'update-files': { method: 'PATCH', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
- 'delete-files': { method: 'DELETE', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
44
- 'files-download': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
45
- 'list-payments': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments', summary: 'Get all payments for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
46
- 'create-payments': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments', summary: 'Create a payment for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
47
- 'get-payments': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Get a specific payment for a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
48
- 'update-payments': { method: 'PATCH', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Update a payment for a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
49
- 'delete-payments': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Delete a payment from a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
50
- 'payments-approve': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/approve', summary: 'Approve a payment entry (OPEN → APPROVED)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
51
- 'payments-reopen': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/reopen', summary: 'Reopen an approved payment entry (APPROVED → OPEN)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
52
- 'payments-cancel': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/cancel', summary: 'Cancel a sent payment entry (SENT -> ERROR)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
53
- 'create-offsets': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/offsets', summary: 'Offset a prepayment or credit note against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
54
- 'delete-offsets': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/offsets/{offsetId}', summary: 'Remove an offset of a prepayment or credit note from a vendor invoice', pathParams: ['vendorInvoiceId', 'offsetId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
55
- 'offset-candidates': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/offset-candidates', summary: 'List the prepayments and credit notes that can be offset against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
56
- 'create-nettings': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/nettings', summary: 'Net a sales invoice of the same business partner against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
57
- 'delete-nettings': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/nettings/{offsetId}', summary: 'Remove a netting of a vendor invoice against a sales invoice', pathParams: ['vendorInvoiceId', 'offsetId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
58
- 'netting-candidates': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/netting-candidates', summary: 'List the sales invoices that can be netted against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/vendor-invoices', summary: 'Get all vendor invoices with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/vendor-invoices', summary: 'Create a new vendor invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'payments'], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/vendor-invoices/{id}', summary: 'Get a vendor invoice by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/vendor-invoices/{id}', summary: 'Update a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/vendor-invoices/{id}', summary: 'Delete a vendor invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'payment-derivation': { method: 'GET', path: '/v1/vendor-invoices/{id}/payment-derivation', summary: 'Explain how a vendor invoice\'s paid amount is derived', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'beneficiary-check': { method: 'GET', path: '/v1/vendor-invoices/{id}/beneficiary-check', summary: 'Check a vendor invoice\'s beneficiary account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'submit': { method: 'POST', path: '/v1/vendor-invoices/{id}/submit', summary: 'Submit a vendor invoice for approval (transitions DRAFT to PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'approve': { method: 'POST', path: '/v1/vendor-invoices/{id}/approve', summary: 'Approve a vendor invoice (transitions DRAFT/PENDING_APPROVAL to APPROVED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'], bodyMediaType: '', specParams: [] },
24
+ 'decline': { method: 'POST', path: '/v1/vendor-invoices/{id}/decline', summary: 'Decline a vendor invoice (transitions PENDING_APPROVAL to DECLINED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'book': { method: 'POST', path: '/v1/vendor-invoices/{id}/book', summary: 'Manually book a vendor invoice (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'rebuild-journal': { method: 'POST', path: '/v1/vendor-invoices/{id}/rebuild-journal', summary: 'Rebuild a vendor invoice booking journal (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'cancel': { method: 'POST', path: '/v1/vendor-invoices/{id}/cancel', summary: 'Cancel a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
28
+ 'replace': { method: 'POST', path: '/v1/vendor-invoices/{id}/replace', summary: 'Replace a vendor invoice with a new draft', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'change-vendor': { method: 'POST', path: '/v1/vendor-invoices/{id}/change-vendor', summary: 'Change the vendor on a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'recalculate-payment-status': { method: 'POST', path: '/v1/vendor-invoices/{id}/recalculate-payment-status', summary: 'Recalculate a vendor invoice\'s open amount and payment status', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'validate': { method: 'POST', path: '/v1/vendor-invoices/{id}/validate', summary: 'Validate a vendor invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'], bodyMediaType: '', specParams: [] },
32
+ 'create-line-items': { method: 'POST', path: '/v1/vendor-invoices/{invoiceId}/line-items', summary: 'Add a line item to a vendor invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'purchaseOrderLineItems', 'creditedVendorInvoiceLineItems'], bodyMediaType: '', specParams: [] },
33
+ 'update-line-items': { method: 'PATCH', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'purchaseOrderLineItems', 'creditedVendorInvoiceLineItems'], bodyMediaType: '', specParams: [] },
34
+ 'delete-line-items': { method: 'DELETE', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Remove a line item from a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
+ 'list-notes': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes', summary: 'Get all notes for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
36
+ 'create-notes': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes', summary: 'Create a note for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'get-notes': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Get a specific note for a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'update-notes': { method: 'PATCH', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Update a note for a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
+ 'delete-notes': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/notes/{noteId}', summary: 'Delete a note from a vendor invoice', pathParams: ['vendorInvoiceId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
40
+ 'list-files': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files', summary: 'List file attachments for a vendor invoice', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
41
+ 'create-files': { method: 'POST', path: '/v1/vendor-invoices/{parentId}/files', summary: 'Upload a file attachment to a vendor invoice', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
42
+ 'get-files': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Get a file attachment for a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
43
+ 'update-files': { method: 'PATCH', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
+ 'delete-files': { method: 'DELETE', path: '/v1/vendor-invoices/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
45
+ 'files-download': { method: 'GET', path: '/v1/vendor-invoices/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a vendor invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
46
+ 'list-payments': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments', summary: 'Get all payments for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
47
+ 'create-payments': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments', summary: 'Create a payment for a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
48
+ 'get-payments': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Get a specific payment for a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
49
+ 'update-payments': { method: 'PATCH', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Update a payment for a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
50
+ 'delete-payments': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}', summary: 'Delete a payment from a vendor invoice', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
51
+ 'payments-approve': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/approve', summary: 'Approve a payment entry (OPEN → APPROVED)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
52
+ 'payments-reopen': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/reopen', summary: 'Reopen an approved payment entry (APPROVED → OPEN)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
53
+ 'payments-cancel': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/payments/{paymentId}/cancel', summary: 'Cancel a sent payment entry (SENT -> ERROR)', pathParams: ['vendorInvoiceId', 'paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
54
+ 'create-offsets': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/offsets', summary: 'Offset a prepayment or credit note against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
55
+ 'delete-offsets': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/offsets/{offsetId}', summary: 'Remove an offset of a prepayment or credit note from a vendor invoice', pathParams: ['vendorInvoiceId', 'offsetId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
56
+ 'offset-candidates': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/offset-candidates', summary: 'List the prepayments and credit notes that can be offset against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
57
+ 'create-nettings': { method: 'POST', path: '/v1/vendor-invoices/{vendorInvoiceId}/nettings', summary: 'Net a sales invoice of the same business partner against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
58
+ 'delete-nettings': { method: 'DELETE', path: '/v1/vendor-invoices/{vendorInvoiceId}/nettings/{offsetId}', summary: 'Remove a netting of a vendor invoice against a sales invoice', pathParams: ['vendorInvoiceId', 'offsetId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
59
+ 'netting-candidates': { method: 'GET', path: '/v1/vendor-invoices/{vendorInvoiceId}/netting-candidates', summary: 'List the sales invoices that can be netted against a vendor invoice', pathParams: ['vendorInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
59
60
  };
60
61
  export default class VendorInvoices extends Command {
61
62
  static description = 'Manage vendor invoices';
@@ -77,7 +78,7 @@ export default class VendorInvoices extends Command {
77
78
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
78
79
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
79
80
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
80
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
81
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
81
82
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
82
83
  'line-item-id': Flags.string({ description: 'Path param: lineItemId' }),
83
84
  'note-id': Flags.string({ description: 'Path param: noteId' }),
@@ -213,6 +214,17 @@ export default class VendorInvoices extends Command {
213
214
  }
214
215
  }
215
216
  }
217
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
218
+ // have them: their body is not JSON, so --set cannot carry a value there.
219
+ for (const param of endpoint.specParams) {
220
+ const value = flags[param.flag];
221
+ if (value === undefined || value === '') {
222
+ if (param.required)
223
+ this.error(`Missing required flag: --${param.flag}`);
224
+ continue;
225
+ }
226
+ queryParams[param.name] = value;
227
+ }
216
228
  let data;
217
229
  if (endpoint.isMultipart) {
218
230
  if (!flags.file)
@@ -233,6 +245,25 @@ export default class VendorInvoices extends Command {
233
245
  }
234
246
  data = form;
235
247
  }
248
+ else if (endpoint.bodyMediaType) {
249
+ // The body is not JSON (today only application/x-ndjson): it is sent
250
+ // verbatim, under the content type the endpoint asks for.
251
+ if (flags.file) {
252
+ const fsModule = await import('node:fs');
253
+ try {
254
+ data = fsModule.readFileSync(flags.file, 'utf-8');
255
+ }
256
+ catch {
257
+ this.error(`Failed to read file: ${flags.file}`);
258
+ }
259
+ }
260
+ else if (flags.data) {
261
+ data = flags.data;
262
+ }
263
+ else {
264
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
265
+ }
266
+ }
236
267
  else if (endpoint.hasBody) {
237
268
  // For binary-response endpoints, --file is the output path, not a JSON
238
269
  // input file — skip the read-as-body branch.
@@ -328,7 +359,7 @@ export default class VendorInvoices extends Command {
328
359
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
329
360
  }
330
361
  else if (data) {
331
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
362
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
332
363
  }
333
364
  process.stderr.write('\n');
334
365
  }
@@ -342,6 +373,7 @@ export default class VendorInvoices extends Command {
342
373
  const binResponse = await apiRequest(endpoint.method, apiPath, {
343
374
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
344
375
  data,
376
+ contentType: endpoint.bodyMediaType || undefined,
345
377
  contextName: flags.context,
346
378
  binary: true,
347
379
  });
@@ -367,6 +399,7 @@ export default class VendorInvoices extends Command {
367
399
  const raw = await apiRequest(endpoint.method, apiPath, {
368
400
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
369
401
  data,
402
+ contentType: endpoint.bodyMediaType || undefined,
370
403
  contextName: flags.context,
371
404
  binary: true,
372
405
  });
@@ -387,6 +420,7 @@ export default class VendorInvoices extends Command {
387
420
  response = await apiRequest(endpoint.method, apiPath, {
388
421
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
389
422
  data,
423
+ contentType: endpoint.bodyMediaType || undefined,
390
424
  contextName: flags.context,
391
425
  acceptAny: true,
392
426
  });
@@ -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 = [...["list", "create", "events", "events-relations", "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/webhooks', summary: 'List all webhook subscriptions', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/webhooks', summary: 'Create a new webhook subscription', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tenantIds'] },
16
- 'events': { method: 'GET', path: '/v1/webhooks/events', summary: 'Get available event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'events-relations': { method: 'GET', path: '/v1/webhooks/events/{eventType}/relations', summary: 'Get valid relations for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'get': { method: 'GET', path: '/v1/webhooks/{id}', summary: 'Get a webhook subscription by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'update': { method: 'PATCH', path: '/v1/webhooks/{id}', summary: 'Update a webhook subscription', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tenantIds'] },
20
- 'delete': { method: 'DELETE', path: '/v1/webhooks/{id}', summary: 'Delete a webhook subscription', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/webhooks', summary: 'List all webhook subscriptions', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/webhooks', summary: 'Create a new webhook subscription', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tenantIds'], bodyMediaType: '', specParams: [] },
17
+ 'events': { method: 'GET', path: '/v1/webhooks/events', summary: 'Get available event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'events-relations': { method: 'GET', path: '/v1/webhooks/events/{eventType}/relations', summary: 'Get valid relations for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'get': { method: 'GET', path: '/v1/webhooks/{id}', summary: 'Get a webhook subscription by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'update': { method: 'PATCH', path: '/v1/webhooks/{id}', summary: 'Update a webhook subscription', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tenantIds'], bodyMediaType: '', specParams: [] },
21
+ 'delete': { method: 'DELETE', path: '/v1/webhooks/{id}', summary: 'Delete a webhook subscription', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
22
  };
22
23
  export default class Webhooks extends Command {
23
24
  static description = 'Manage webhooks';
@@ -39,7 +40,7 @@ export default class Webhooks extends Command {
39
40
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
40
41
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
41
42
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
42
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
43
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
43
44
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
44
45
  page: Flags.string({ description: 'Page number' }),
45
46
  limit: Flags.string({ description: 'Items per page' }),
@@ -170,6 +171,17 @@ export default class Webhooks extends Command {
170
171
  }
171
172
  }
172
173
  }
174
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
175
+ // have them: their body is not JSON, so --set cannot carry a value there.
176
+ for (const param of endpoint.specParams) {
177
+ const value = flags[param.flag];
178
+ if (value === undefined || value === '') {
179
+ if (param.required)
180
+ this.error(`Missing required flag: --${param.flag}`);
181
+ continue;
182
+ }
183
+ queryParams[param.name] = value;
184
+ }
173
185
  let data;
174
186
  if (endpoint.isMultipart) {
175
187
  if (!flags.file)
@@ -190,6 +202,25 @@ export default class Webhooks extends Command {
190
202
  }
191
203
  data = form;
192
204
  }
205
+ else if (endpoint.bodyMediaType) {
206
+ // The body is not JSON (today only application/x-ndjson): it is sent
207
+ // verbatim, under the content type the endpoint asks for.
208
+ if (flags.file) {
209
+ const fsModule = await import('node:fs');
210
+ try {
211
+ data = fsModule.readFileSync(flags.file, 'utf-8');
212
+ }
213
+ catch {
214
+ this.error(`Failed to read file: ${flags.file}`);
215
+ }
216
+ }
217
+ else if (flags.data) {
218
+ data = flags.data;
219
+ }
220
+ else {
221
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
222
+ }
223
+ }
193
224
  else if (endpoint.hasBody) {
194
225
  // For binary-response endpoints, --file is the output path, not a JSON
195
226
  // input file — skip the read-as-body branch.
@@ -285,7 +316,7 @@ export default class Webhooks extends Command {
285
316
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
286
317
  }
287
318
  else if (data) {
288
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
319
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
289
320
  }
290
321
  process.stderr.write('\n');
291
322
  }
@@ -299,6 +330,7 @@ export default class Webhooks extends Command {
299
330
  const binResponse = await apiRequest(endpoint.method, apiPath, {
300
331
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
301
332
  data,
333
+ contentType: endpoint.bodyMediaType || undefined,
302
334
  contextName: flags.context,
303
335
  binary: true,
304
336
  });
@@ -324,6 +356,7 @@ export default class Webhooks extends Command {
324
356
  const raw = await apiRequest(endpoint.method, apiPath, {
325
357
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
326
358
  data,
359
+ contentType: endpoint.bodyMediaType || undefined,
327
360
  contextName: flags.context,
328
361
  binary: true,
329
362
  });
@@ -344,6 +377,7 @@ export default class Webhooks extends Command {
344
377
  response = await apiRequest(endpoint.method, apiPath, {
345
378
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
346
379
  data,
380
+ contentType: endpoint.bodyMediaType || undefined,
347
381
  contextName: flags.context,
348
382
  acceptAny: true,
349
383
  });
@@ -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,7 +12,7 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["get"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'get': { method: 'GET', path: '/v1/work-summary', summary: 'Count what is open for the caller', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'get': { method: 'GET', path: '/v1/work-summary', summary: 'Count what is open for the caller', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
15
16
  };
16
17
  export default class WorkSummary extends Command {
17
18
  static description = 'Manage work summary';
@@ -28,7 +29,7 @@ export default class WorkSummary extends Command {
28
29
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
29
30
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
30
31
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
31
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
32
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
32
33
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
33
34
  page: Flags.string({ description: 'Page number' }),
34
35
  limit: Flags.string({ description: 'Items per page' }),
@@ -159,6 +160,17 @@ export default class WorkSummary extends Command {
159
160
  }
160
161
  }
161
162
  }
163
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
164
+ // have them: their body is not JSON, so --set cannot carry a value there.
165
+ for (const param of endpoint.specParams) {
166
+ const value = flags[param.flag];
167
+ if (value === undefined || value === '') {
168
+ if (param.required)
169
+ this.error(`Missing required flag: --${param.flag}`);
170
+ continue;
171
+ }
172
+ queryParams[param.name] = value;
173
+ }
162
174
  let data;
163
175
  if (endpoint.isMultipart) {
164
176
  if (!flags.file)
@@ -179,6 +191,25 @@ export default class WorkSummary extends Command {
179
191
  }
180
192
  data = form;
181
193
  }
194
+ else if (endpoint.bodyMediaType) {
195
+ // The body is not JSON (today only application/x-ndjson): it is sent
196
+ // verbatim, under the content type the endpoint asks for.
197
+ if (flags.file) {
198
+ const fsModule = await import('node:fs');
199
+ try {
200
+ data = fsModule.readFileSync(flags.file, 'utf-8');
201
+ }
202
+ catch {
203
+ this.error(`Failed to read file: ${flags.file}`);
204
+ }
205
+ }
206
+ else if (flags.data) {
207
+ data = flags.data;
208
+ }
209
+ else {
210
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
211
+ }
212
+ }
182
213
  else if (endpoint.hasBody) {
183
214
  // For binary-response endpoints, --file is the output path, not a JSON
184
215
  // input file — skip the read-as-body branch.
@@ -274,7 +305,7 @@ export default class WorkSummary extends Command {
274
305
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
275
306
  }
276
307
  else if (data) {
277
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
308
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
278
309
  }
279
310
  process.stderr.write('\n');
280
311
  }
@@ -288,6 +319,7 @@ export default class WorkSummary extends Command {
288
319
  const binResponse = await apiRequest(endpoint.method, apiPath, {
289
320
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
290
321
  data,
322
+ contentType: endpoint.bodyMediaType || undefined,
291
323
  contextName: flags.context,
292
324
  binary: true,
293
325
  });
@@ -313,6 +345,7 @@ export default class WorkSummary extends Command {
313
345
  const raw = await apiRequest(endpoint.method, apiPath, {
314
346
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
315
347
  data,
348
+ contentType: endpoint.bodyMediaType || undefined,
316
349
  contextName: flags.context,
317
350
  binary: true,
318
351
  });
@@ -333,6 +366,7 @@ export default class WorkSummary extends Command {
333
366
  response = await apiRequest(endpoint.method, apiPath, {
334
367
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
335
368
  data,
369
+ contentType: endpoint.bodyMediaType || undefined,
336
370
  contextName: flags.context,
337
371
  acceptAny: true,
338
372
  });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Expands a leading `~` to the user's home directory.
3
+ *
4
+ * The shell only expands a tilde that starts a word, so `-f ~/log.jsonl`
5
+ * arrives expanded but `--file=~/log.jsonl` reaches us verbatim and would
6
+ * otherwise be looked up as a directory called `~`.
7
+ */
8
+ export declare function resolveUserPath(input: string): string;
@@ -0,0 +1,16 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ /**
4
+ * Expands a leading `~` to the user's home directory.
5
+ *
6
+ * The shell only expands a tilde that starts a word, so `-f ~/log.jsonl`
7
+ * arrives expanded but `--file=~/log.jsonl` reaches us verbatim and would
8
+ * otherwise be looked up as a directory called `~`.
9
+ */
10
+ export function resolveUserPath(input) {
11
+ if (input === '~')
12
+ return os.homedir();
13
+ if (input.startsWith('~/'))
14
+ return path.join(os.homedir(), input.slice(2));
15
+ return input;
16
+ }
@@ -26,6 +26,8 @@ function nonEmpty(value) {
26
26
  function readStoredAccount(env, homedir) {
27
27
  const dir = env.CLAUDE_CONFIG_DIR ? path.resolve(env.CLAUDE_CONFIG_DIR) : homedir;
28
28
  try {
29
+ // The directory is the local user's own CLAUDE_CONFIG_DIR or home, and the file name is fixed.
30
+ // nosemgrep: AIK_ts_generic_path_traversal
29
31
  const parsed = JSON.parse(fs.readFileSync(path.join(dir, '.claude.json'), 'utf8'));
30
32
  const account = parsed.oauthAccount;
31
33
  if (!account || typeof account !== 'object')