@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.
- package/dist/commands/access-groups.js +42 -8
- package/dist/commands/account-transaction-allocations.js +38 -4
- package/dist/commands/account-transactions.d.ts +2 -0
- package/dist/commands/account-transactions.js +68 -32
- package/dist/commands/accounts.js +46 -12
- package/dist/commands/agent-platform.js +37 -3
- package/dist/commands/agent-runs.js +49 -15
- package/dist/commands/analytics.js +39 -5
- package/dist/commands/api-clients.js +47 -13
- package/dist/commands/api-keys.js +40 -6
- package/dist/commands/async-jobs.js +45 -11
- package/dist/commands/audit-log.js +38 -4
- package/dist/commands/bank-accounts.js +54 -20
- package/dist/commands/bank-integrations.js +56 -22
- package/dist/commands/business-partners.js +77 -43
- package/dist/commands/chat.js +43 -9
- package/dist/commands/credit-card-accounts.js +57 -23
- package/dist/commands/currencies.js +37 -3
- package/dist/commands/custody-accounts.js +41 -7
- package/dist/commands/dashboards.js +52 -18
- package/dist/commands/delivery-notes.js +69 -35
- package/dist/commands/document-checksums.d.ts +1 -0
- package/dist/commands/document-checksums.js +38 -3
- package/dist/commands/dunning-configs.js +41 -7
- package/dist/commands/dunning-reminders.js +44 -10
- package/dist/commands/dunning-runs.js +37 -3
- package/dist/commands/email-transactions.js +39 -5
- package/dist/commands/employees.js +77 -43
- package/dist/commands/entity-configurations.js +39 -5
- package/dist/commands/event-mapping-rules.js +37 -3
- package/dist/commands/event-processor-mappings.js +41 -7
- package/dist/commands/files.js +48 -14
- package/dist/commands/financial-instrument-transactions.d.ts +56 -0
- package/dist/commands/financial-instrument-transactions.js +1369 -0
- package/dist/commands/financial-instrument-types.js +41 -7
- package/dist/commands/financial-instruments.js +41 -7
- package/dist/commands/health.js +41 -7
- package/dist/commands/imports.js +39 -5
- package/dist/commands/inbox.js +42 -8
- package/dist/commands/integrations.js +39 -5
- package/dist/commands/item-categories.js +41 -7
- package/dist/commands/items.js +42 -8
- package/dist/commands/journal-lines.js +37 -3
- package/dist/commands/journals.js +61 -27
- package/dist/commands/kpis.js +44 -10
- package/dist/commands/ledgers.js +83 -49
- package/dist/commands/my-account.js +54 -20
- package/dist/commands/notifications.js +37 -3
- package/dist/commands/office-locations.js +41 -7
- package/dist/commands/payment-methods.js +46 -12
- package/dist/commands/payment-runs.js +54 -20
- package/dist/commands/payment-terms.js +42 -8
- package/dist/commands/payments.js +43 -9
- package/dist/commands/payroll-runs.js +71 -37
- package/dist/commands/permissions.js +37 -3
- package/dist/commands/price-lists.js +48 -14
- package/dist/commands/processor-mappings.js +40 -6
- package/dist/commands/processors.js +46 -12
- package/dist/commands/purchase-order-types.js +41 -7
- package/dist/commands/purchase-orders.js +60 -26
- package/dist/commands/reports.js +43 -8
- package/dist/commands/roles.js +40 -6
- package/dist/commands/salary-types.js +41 -7
- package/dist/commands/sales-invoices.js +85 -51
- package/dist/commands/sales-orders.js +69 -35
- package/dist/commands/saved-views.js +39 -5
- package/dist/commands/scheduled-processor-mappings.js +41 -7
- package/dist/commands/schedules.js +45 -11
- package/dist/commands/settings.js +38 -4
- package/dist/commands/tags.js +42 -8
- package/dist/commands/tax-codes.js +44 -10
- package/dist/commands/tax-matrix.js +41 -7
- package/dist/commands/templates.js +49 -15
- package/dist/commands/tenants.js +44 -10
- package/dist/commands/tools.js +38 -4
- package/dist/commands/users.js +58 -24
- package/dist/commands/vat-declarations.js +61 -27
- package/dist/commands/vendor-invoices.js +81 -47
- package/dist/commands/webhooks.js +43 -9
- package/dist/commands/work-summary.js +37 -3
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/production-guard.js +2 -0
- package/oclif.manifest.json +199 -2
- 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')
|