@aderis-erp/cli 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/access-groups.js +42 -8
  3. package/dist/commands/account-transaction-allocations.js +38 -4
  4. package/dist/commands/account-transactions.d.ts +2 -0
  5. package/dist/commands/account-transactions.js +68 -32
  6. package/dist/commands/accounts.js +46 -12
  7. package/dist/commands/agent-platform.js +37 -3
  8. package/dist/commands/agent-runs.js +49 -15
  9. package/dist/commands/analytics.js +39 -5
  10. package/dist/commands/api-clients.d.ts +58 -0
  11. package/dist/commands/api-clients.js +1375 -0
  12. package/dist/commands/api-keys.js +40 -6
  13. package/dist/commands/async-jobs.js +45 -11
  14. package/dist/commands/audit-log.js +38 -4
  15. package/dist/commands/bank-accounts.js +54 -20
  16. package/dist/commands/bank-integrations.js +56 -22
  17. package/dist/commands/business-partners.js +77 -43
  18. package/dist/commands/chat.js +43 -9
  19. package/dist/commands/credit-card-accounts.d.ts +4 -0
  20. package/dist/commands/credit-card-accounts.js +64 -25
  21. package/dist/commands/currencies.d.ts +56 -0
  22. package/dist/commands/currencies.js +1361 -0
  23. package/dist/commands/custody-accounts.d.ts +56 -0
  24. package/dist/commands/custody-accounts.js +1369 -0
  25. package/dist/commands/{integration-identities.d.ts → dashboards.d.ts} +3 -3
  26. package/dist/commands/dashboards.js +1381 -0
  27. package/dist/commands/delivery-notes.js +69 -35
  28. package/dist/commands/document-checksums.d.ts +57 -0
  29. package/dist/commands/{integration-identities.js → document-checksums.js} +48 -21
  30. package/dist/commands/dunning-configs.js +41 -7
  31. package/dist/commands/dunning-reminders.js +44 -10
  32. package/dist/commands/dunning-runs.js +37 -3
  33. package/dist/commands/email-transactions.js +39 -5
  34. package/dist/commands/employees.js +77 -43
  35. package/dist/commands/entity-configurations.js +39 -5
  36. package/dist/commands/event-mapping-rules.js +37 -3
  37. package/dist/commands/event-processor-mappings.js +41 -7
  38. package/dist/commands/files.js +48 -14
  39. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  40. package/dist/commands/financial-instrument-transactions.js +1369 -0
  41. package/dist/commands/financial-instrument-types.d.ts +56 -0
  42. package/dist/commands/financial-instrument-types.js +1369 -0
  43. package/dist/commands/financial-instruments.d.ts +56 -0
  44. package/dist/commands/financial-instruments.js +1369 -0
  45. package/dist/commands/health.js +41 -7
  46. package/dist/commands/imports.js +39 -5
  47. package/dist/commands/inbox.js +42 -8
  48. package/dist/commands/integrations.js +39 -5
  49. package/dist/commands/item-categories.js +41 -7
  50. package/dist/commands/items.js +42 -8
  51. package/dist/commands/journal-lines.js +37 -3
  52. package/dist/commands/journals.js +61 -27
  53. package/dist/commands/{workflows.d.ts → kpis.d.ts} +1 -1
  54. package/dist/commands/kpis.js +1371 -0
  55. package/dist/commands/ledgers.js +83 -49
  56. package/dist/commands/my-account.js +54 -20
  57. package/dist/commands/notifications.d.ts +56 -0
  58. package/dist/commands/notifications.js +1360 -0
  59. package/dist/commands/office-locations.js +41 -7
  60. package/dist/commands/payment-methods.js +46 -12
  61. package/dist/commands/payment-runs.js +54 -20
  62. package/dist/commands/payment-terms.js +42 -8
  63. package/dist/commands/payments.js +43 -9
  64. package/dist/commands/payroll-runs.js +71 -37
  65. package/dist/commands/permissions.js +37 -3
  66. package/dist/commands/price-lists.js +48 -14
  67. package/dist/commands/processor-mappings.js +40 -6
  68. package/dist/commands/processors.js +46 -12
  69. package/dist/commands/purchase-order-types.js +41 -7
  70. package/dist/commands/purchase-orders.js +60 -26
  71. package/dist/commands/reports.js +43 -8
  72. package/dist/commands/roles.js +40 -6
  73. package/dist/commands/salary-types.js +41 -7
  74. package/dist/commands/sales-invoices.js +85 -51
  75. package/dist/commands/sales-orders.js +69 -35
  76. package/dist/commands/saved-views.js +39 -5
  77. package/dist/commands/scheduled-processor-mappings.js +41 -7
  78. package/dist/commands/schedules.js +45 -11
  79. package/dist/commands/settings.d.ts +56 -0
  80. package/dist/commands/settings.js +1361 -0
  81. package/dist/commands/tags.js +42 -8
  82. package/dist/commands/tax-codes.js +44 -10
  83. package/dist/commands/tax-matrix.js +41 -7
  84. package/dist/commands/templates.js +50 -15
  85. package/dist/commands/tenants.js +44 -10
  86. package/dist/commands/tools.js +38 -4
  87. package/dist/commands/users.js +58 -24
  88. package/dist/commands/vat-declarations.js +61 -27
  89. package/dist/commands/vendor-invoices.js +82 -47
  90. package/dist/commands/webhooks.js +43 -9
  91. package/dist/commands/work-summary.d.ts +56 -0
  92. package/dist/commands/{workflows.js → work-summary.js} +49 -18
  93. package/dist/lib/auth/index.js +5 -0
  94. package/dist/lib/paths.d.ts +8 -0
  95. package/dist/lib/paths.js +16 -0
  96. package/dist/lib/production-guard.d.ts +42 -0
  97. package/dist/lib/production-guard.js +94 -0
  98. package/oclif.manifest.json +2142 -333
  99. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,41 +12,41 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = ["notes", "files", "files-parentId"];
12
13
  const SUBCOMMANDS = [...["list-notes", "create-notes", "update-notes", "delete-notes", "notices-dismiss", "notices-restore", "list", "create", "get", "update", "delete", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-bookings", "update-bookings", "list-entries", "get-entries", "update-entries", "entries-exclude", "entries-include", "list-files-parentId", "create-files-parentId", "get-files-parentId", "update-files-parentId", "delete-files-parentId", "files-download-parentId", "approve", "cancel", "payments", "recalculate-payment-status", "payslips-send"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list-notes': { method: 'GET', path: '/v1/payroll-runs/notes', summary: 'List payroll notes (paginated), newest effectiveDate first', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create-notes': { method: 'POST', path: '/v1/payroll-runs/notes', summary: 'Create a payroll note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'update-notes': { method: 'PATCH', path: '/v1/payroll-runs/notes/{noteId}', summary: 'Update a payroll note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'delete-notes': { method: 'DELETE', path: '/v1/payroll-runs/notes/{noteId}', summary: 'Delete a payroll note', pathParams: ['noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'notices-dismiss': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/notices/{noticeId}/dismiss', summary: 'Dismiss one review notice on a DRAFT run', pathParams: ['payrollRunId', 'noticeId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'notices-restore': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/notices/{noticeId}/restore', summary: 'Restore a dismissed review notice on a DRAFT run', pathParams: ['payrollRunId', 'noticeId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'list': { method: 'GET', path: '/v1/payroll-runs', summary: 'List payroll runs (paginated), each with its computed totals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'create': { method: 'POST', path: '/v1/payroll-runs', summary: 'Create a payroll run with its entries and DRAFT payments', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['entries'] },
22
- 'get': { method: 'GET', path: '/v1/payroll-runs/{id}', summary: 'Get a payroll run with its totals, previous-run comparison and payments', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'update': { method: 'PATCH', path: '/v1/payroll-runs/{id}', summary: 'Change the payout date, description or bank account of a DRAFT payroll run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'delete': { method: 'DELETE', path: '/v1/payroll-runs/{id}', summary: 'Delete a DRAFT payroll run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'list-files': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files', summary: 'List file attachments for a payroll run', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'create-files': { method: 'POST', path: '/v1/payroll-runs/{parentId}/files', summary: 'Upload a file attachment to a payroll run', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
27
- 'get-files': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Get a file attachment for a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'update-files': { method: 'PATCH', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a payroll run', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'delete-files': { method: 'DELETE', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'files-download': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
31
- 'list-bookings': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/bookings', summary: 'List a payroll run\'s bookings, one per open ledger, and the clearing check', pathParams: ['payrollRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
32
- 'update-bookings': { method: 'PATCH', path: '/v1/payroll-runs/{payrollRunId}/bookings/{bookingId}', summary: 'Replace the lines of one of a DRAFT run\'s bookings by hand', pathParams: ['payrollRunId', 'bookingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'] },
33
- 'list-entries': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/entries', summary: 'List a payroll run\'s entries, each with its previous-run comparison', pathParams: ['payrollRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'get-entries': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}', summary: 'Get one payroll run entry with its previous-run comparison', pathParams: ['payrollRunId', 'entryId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
35
- 'update-entries': { method: 'PATCH', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}', summary: 'Change a DRAFT entry\'s notes or payoutDate', pathParams: ['payrollRunId', 'entryId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'entries-exclude': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}/exclude', summary: 'Exclude a DRAFT entry from the payout', pathParams: ['payrollRunId', 'entryId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'entries-include': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}/include', summary: 'Include a previously excluded DRAFT entry back into the payout', pathParams: ['payrollRunId', 'entryId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
38
- 'list-files-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files', summary: 'List file attachments for a payroll run entry', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
39
- 'create-files-parentId': { method: 'POST', path: '/v1/payroll-run-entries/{parentId}/files', summary: 'Upload a file attachment to a payroll run entry', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
40
- 'get-files-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Get a file attachment for a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
- 'update-files-parentId': { method: 'PATCH', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
- 'delete-files-parentId': { method: 'DELETE', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
- 'files-download-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
44
- 'approve': { method: 'POST', path: '/v1/payroll-runs/{id}/approve', summary: 'Approve a DRAFT payroll run and open its payment run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
45
- 'cancel': { method: 'POST', path: '/v1/payroll-runs/{id}/cancel', summary: 'Cancel an APPROVED payroll run whose payments have not been executed', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
46
- 'payments': { method: 'GET', path: '/v1/payroll-runs/{id}/payments', summary: 'List every payment of a payroll run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
47
- 'recalculate-payment-status': { method: 'POST', path: '/v1/payroll-runs/{id}/recalculate-payment-status', summary: 'Recalculate a payroll run\'s payment status from its payments', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
48
- 'payslips-send': { method: 'POST', path: '/v1/payroll-runs/{id}/payslips/send', summary: 'Email employees their payslips for an APPROVED payroll run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['entryIds'] },
15
+ 'list-notes': { method: 'GET', path: '/v1/payroll-runs/notes', summary: 'List payroll notes (paginated), newest effectiveDate first', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create-notes': { method: 'POST', path: '/v1/payroll-runs/notes', summary: 'Create a payroll note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'update-notes': { method: 'PATCH', path: '/v1/payroll-runs/notes/{noteId}', summary: 'Update a payroll note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'delete-notes': { method: 'DELETE', path: '/v1/payroll-runs/notes/{noteId}', summary: 'Delete a payroll note', pathParams: ['noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'notices-dismiss': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/notices/{noticeId}/dismiss', summary: 'Dismiss one review notice on a DRAFT run', pathParams: ['payrollRunId', 'noticeId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'notices-restore': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/notices/{noticeId}/restore', summary: 'Restore a dismissed review notice on a DRAFT run', pathParams: ['payrollRunId', 'noticeId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'list': { method: 'GET', path: '/v1/payroll-runs', summary: 'List payroll runs (paginated), each with its computed totals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'create': { method: 'POST', path: '/v1/payroll-runs', summary: 'Create a payroll run with its entries and DRAFT payments', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['entries'], bodyMediaType: '', specParams: [] },
23
+ 'get': { method: 'GET', path: '/v1/payroll-runs/{id}', summary: 'Get a payroll run with its totals, previous-run comparison and payments', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'update': { method: 'PATCH', path: '/v1/payroll-runs/{id}', summary: 'Change the payout date, description or bank account of a DRAFT payroll run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'delete': { method: 'DELETE', path: '/v1/payroll-runs/{id}', summary: 'Delete a DRAFT payroll run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'list-files': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files', summary: 'List file attachments for a payroll run', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'create-files': { method: 'POST', path: '/v1/payroll-runs/{parentId}/files', summary: 'Upload a file attachment to a payroll run', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
28
+ 'get-files': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Get a file attachment for a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'update-files': { method: 'PATCH', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a payroll run', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'delete-files': { method: 'DELETE', path: '/v1/payroll-runs/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'files-download': { method: 'GET', path: '/v1/payroll-runs/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a payroll run', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
32
+ 'list-bookings': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/bookings', summary: 'List a payroll run\'s bookings, one per open ledger, and the clearing check', pathParams: ['payrollRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
33
+ 'update-bookings': { method: 'PATCH', path: '/v1/payroll-runs/{payrollRunId}/bookings/{bookingId}', summary: 'Replace the lines of one of a DRAFT run\'s bookings by hand', pathParams: ['payrollRunId', 'bookingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'], bodyMediaType: '', specParams: [] },
34
+ 'list-entries': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/entries', summary: 'List a payroll run\'s entries, each with its previous-run comparison', pathParams: ['payrollRunId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
+ 'get-entries': { method: 'GET', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}', summary: 'Get one payroll run entry with its previous-run comparison', pathParams: ['payrollRunId', 'entryId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
36
+ 'update-entries': { method: 'PATCH', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}', summary: 'Change a DRAFT entry\'s notes or payoutDate', pathParams: ['payrollRunId', 'entryId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'entries-exclude': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}/exclude', summary: 'Exclude a DRAFT entry from the payout', pathParams: ['payrollRunId', 'entryId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'entries-include': { method: 'POST', path: '/v1/payroll-runs/{payrollRunId}/entries/{entryId}/include', summary: 'Include a previously excluded DRAFT entry back into the payout', pathParams: ['payrollRunId', 'entryId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
+ 'list-files-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files', summary: 'List file attachments for a payroll run entry', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
40
+ 'create-files-parentId': { method: 'POST', path: '/v1/payroll-run-entries/{parentId}/files', summary: 'Upload a file attachment to a payroll run entry', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
41
+ 'get-files-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Get a file attachment for a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
42
+ 'update-files-parentId': { method: 'PATCH', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
43
+ 'delete-files-parentId': { method: 'DELETE', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
+ 'files-download-parentId': { method: 'GET', path: '/v1/payroll-run-entries/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a payroll run entry', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
45
+ 'approve': { method: 'POST', path: '/v1/payroll-runs/{id}/approve', summary: 'Approve a DRAFT payroll run and open its payment run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
46
+ 'cancel': { method: 'POST', path: '/v1/payroll-runs/{id}/cancel', summary: 'Cancel an APPROVED payroll run whose payments have not been executed', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
47
+ 'payments': { method: 'GET', path: '/v1/payroll-runs/{id}/payments', summary: 'List every payment of a payroll run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
48
+ 'recalculate-payment-status': { method: 'POST', path: '/v1/payroll-runs/{id}/recalculate-payment-status', summary: 'Recalculate a payroll run\'s payment status from its payments', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
49
+ 'payslips-send': { method: 'POST', path: '/v1/payroll-runs/{id}/payslips/send', summary: 'Email employees their payslips for an APPROVED payroll run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['entryIds'], bodyMediaType: '', specParams: [] },
49
50
  };
50
51
  export default class PayrollRuns extends Command {
51
52
  static description = 'Manage payroll runs';
@@ -67,7 +68,7 @@ export default class PayrollRuns extends Command {
67
68
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
68
69
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
69
70
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
70
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
71
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
71
72
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
72
73
  'notice-id': Flags.string({ description: 'Path param: noticeId' }),
73
74
  'file-id': Flags.string({ description: 'Path param: fileId' }),
@@ -202,6 +203,17 @@ export default class PayrollRuns extends Command {
202
203
  }
203
204
  }
204
205
  }
206
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
207
+ // have them: their body is not JSON, so --set cannot carry a value there.
208
+ for (const param of endpoint.specParams) {
209
+ const value = flags[param.flag];
210
+ if (value === undefined || value === '') {
211
+ if (param.required)
212
+ this.error(`Missing required flag: --${param.flag}`);
213
+ continue;
214
+ }
215
+ queryParams[param.name] = value;
216
+ }
205
217
  let data;
206
218
  if (endpoint.isMultipart) {
207
219
  if (!flags.file)
@@ -222,6 +234,25 @@ export default class PayrollRuns extends Command {
222
234
  }
223
235
  data = form;
224
236
  }
237
+ else if (endpoint.bodyMediaType) {
238
+ // The body is not JSON (today only application/x-ndjson): it is sent
239
+ // verbatim, under the content type the endpoint asks for.
240
+ if (flags.file) {
241
+ const fsModule = await import('node:fs');
242
+ try {
243
+ data = fsModule.readFileSync(flags.file, 'utf-8');
244
+ }
245
+ catch {
246
+ this.error(`Failed to read file: ${flags.file}`);
247
+ }
248
+ }
249
+ else if (flags.data) {
250
+ data = flags.data;
251
+ }
252
+ else {
253
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
254
+ }
255
+ }
225
256
  else if (endpoint.hasBody) {
226
257
  // For binary-response endpoints, --file is the output path, not a JSON
227
258
  // input file — skip the read-as-body branch.
@@ -317,7 +348,7 @@ export default class PayrollRuns extends Command {
317
348
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
318
349
  }
319
350
  else if (data) {
320
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
351
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
321
352
  }
322
353
  process.stderr.write('\n');
323
354
  }
@@ -331,6 +362,7 @@ export default class PayrollRuns extends Command {
331
362
  const binResponse = await apiRequest(endpoint.method, apiPath, {
332
363
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
333
364
  data,
365
+ contentType: endpoint.bodyMediaType || undefined,
334
366
  contextName: flags.context,
335
367
  binary: true,
336
368
  });
@@ -356,6 +388,7 @@ export default class PayrollRuns extends Command {
356
388
  const raw = await apiRequest(endpoint.method, apiPath, {
357
389
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
358
390
  data,
391
+ contentType: endpoint.bodyMediaType || undefined,
359
392
  contextName: flags.context,
360
393
  binary: true,
361
394
  });
@@ -376,6 +409,7 @@ export default class PayrollRuns extends Command {
376
409
  response = await apiRequest(endpoint.method, apiPath, {
377
410
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
378
411
  data,
412
+ contentType: endpoint.bodyMediaType || undefined,
379
413
  contextName: flags.context,
380
414
  acceptAny: true,
381
415
  });
@@ -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 = [...["list"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/permissions', summary: 'List permissions selectable for tenant-created roles', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/permissions', summary: 'List permissions selectable for tenant-created roles', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
15
16
  };
16
17
  export default class Permissions extends Command {
17
18
  static description = 'Manage permissions';
@@ -29,7 +30,7 @@ export default class Permissions extends Command {
29
30
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
30
31
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
31
32
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
32
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
33
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
33
34
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
34
35
  page: Flags.string({ description: 'Page number' }),
35
36
  limit: Flags.string({ description: 'Items per page' }),
@@ -160,6 +161,17 @@ export default class Permissions extends Command {
160
161
  }
161
162
  }
162
163
  }
164
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
165
+ // have them: their body is not JSON, so --set cannot carry a value there.
166
+ for (const param of endpoint.specParams) {
167
+ const value = flags[param.flag];
168
+ if (value === undefined || value === '') {
169
+ if (param.required)
170
+ this.error(`Missing required flag: --${param.flag}`);
171
+ continue;
172
+ }
173
+ queryParams[param.name] = value;
174
+ }
163
175
  let data;
164
176
  if (endpoint.isMultipart) {
165
177
  if (!flags.file)
@@ -180,6 +192,25 @@ export default class Permissions extends Command {
180
192
  }
181
193
  data = form;
182
194
  }
195
+ else if (endpoint.bodyMediaType) {
196
+ // The body is not JSON (today only application/x-ndjson): it is sent
197
+ // verbatim, under the content type the endpoint asks for.
198
+ if (flags.file) {
199
+ const fsModule = await import('node:fs');
200
+ try {
201
+ data = fsModule.readFileSync(flags.file, 'utf-8');
202
+ }
203
+ catch {
204
+ this.error(`Failed to read file: ${flags.file}`);
205
+ }
206
+ }
207
+ else if (flags.data) {
208
+ data = flags.data;
209
+ }
210
+ else {
211
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
212
+ }
213
+ }
183
214
  else if (endpoint.hasBody) {
184
215
  // For binary-response endpoints, --file is the output path, not a JSON
185
216
  // input file — skip the read-as-body branch.
@@ -275,7 +306,7 @@ export default class Permissions extends Command {
275
306
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
276
307
  }
277
308
  else if (data) {
278
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
309
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
279
310
  }
280
311
  process.stderr.write('\n');
281
312
  }
@@ -289,6 +320,7 @@ export default class Permissions extends Command {
289
320
  const binResponse = await apiRequest(endpoint.method, apiPath, {
290
321
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
291
322
  data,
323
+ contentType: endpoint.bodyMediaType || undefined,
292
324
  contextName: flags.context,
293
325
  binary: true,
294
326
  });
@@ -314,6 +346,7 @@ export default class Permissions extends Command {
314
346
  const raw = await apiRequest(endpoint.method, apiPath, {
315
347
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
316
348
  data,
349
+ contentType: endpoint.bodyMediaType || undefined,
317
350
  contextName: flags.context,
318
351
  binary: true,
319
352
  });
@@ -334,6 +367,7 @@ export default class Permissions extends Command {
334
367
  response = await apiRequest(endpoint.method, apiPath, {
335
368
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
336
369
  data,
370
+ contentType: endpoint.bodyMediaType || undefined,
337
371
  contextName: flags.context,
338
372
  acceptAny: true,
339
373
  });
@@ -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,18 +12,18 @@ const SERVER_IMPORTS = ["import", "import-prices"];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-prices", "create-prices", "get-prices", "update-prices", "delete-prices", "import", "import-prices"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/price-lists', summary: 'Get all price lists with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/price-lists', summary: 'Create a new price list', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'get': { method: 'GET', path: '/v1/price-lists/{id}', summary: 'Get a price list by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/price-lists/{id}', summary: 'Update a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'delete': { method: 'DELETE', path: '/v1/price-lists/{id}', summary: 'Delete a price list', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'list-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices', summary: 'Get all prices in a price list with pagination', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'create-prices': { method: 'POST', path: '/v1/price-lists/{id}/prices', summary: 'Create a new price in a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'get-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Get a price by ID', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'update-prices': { method: 'PATCH', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Update a price', pathParams: ['id', 'priceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'delete-prices': { method: 'DELETE', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Delete a price', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'import': { method: 'POST', path: '/v1/price-lists/import', summary: 'Import price lists', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'import-prices': { method: 'POST', path: '/v1/price-lists/{priceListId}/prices/import', summary: 'Import prices into a price list', pathParams: ['priceListId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/price-lists', summary: 'Get all price lists with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/price-lists', summary: 'Create a new price list', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/price-lists/{id}', summary: 'Get a price list by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/price-lists/{id}', summary: 'Update a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/price-lists/{id}', summary: 'Delete a price list', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'list-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices', summary: 'Get all prices in a price list with pagination', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'create-prices': { method: 'POST', path: '/v1/price-lists/{id}/prices', summary: 'Create a new price in a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'get-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Get a price by ID', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'update-prices': { method: 'PATCH', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Update a price', pathParams: ['id', 'priceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'delete-prices': { method: 'DELETE', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Delete a price', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'import': { method: 'POST', path: '/v1/price-lists/import', summary: 'Import price lists', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'import-prices': { method: 'POST', path: '/v1/price-lists/{priceListId}/prices/import', summary: 'Import prices into a price list', pathParams: ['priceListId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
27
  };
27
28
  export default class PriceLists extends Command {
28
29
  static description = 'Manage price lists';
@@ -44,7 +45,7 @@ export default class PriceLists extends Command {
44
45
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
45
46
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
46
47
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
47
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
48
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
48
49
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
49
50
  mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
50
51
  reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
@@ -180,6 +181,17 @@ export default class PriceLists extends Command {
180
181
  }
181
182
  }
182
183
  }
184
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
185
+ // have them: their body is not JSON, so --set cannot carry a value there.
186
+ for (const param of endpoint.specParams) {
187
+ const value = flags[param.flag];
188
+ if (value === undefined || value === '') {
189
+ if (param.required)
190
+ this.error(`Missing required flag: --${param.flag}`);
191
+ continue;
192
+ }
193
+ queryParams[param.name] = value;
194
+ }
183
195
  let data;
184
196
  if (endpoint.isMultipart) {
185
197
  if (!flags.file)
@@ -200,6 +212,25 @@ export default class PriceLists extends Command {
200
212
  }
201
213
  data = form;
202
214
  }
215
+ else if (endpoint.bodyMediaType) {
216
+ // The body is not JSON (today only application/x-ndjson): it is sent
217
+ // verbatim, under the content type the endpoint asks for.
218
+ if (flags.file) {
219
+ const fsModule = await import('node:fs');
220
+ try {
221
+ data = fsModule.readFileSync(flags.file, 'utf-8');
222
+ }
223
+ catch {
224
+ this.error(`Failed to read file: ${flags.file}`);
225
+ }
226
+ }
227
+ else if (flags.data) {
228
+ data = flags.data;
229
+ }
230
+ else {
231
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
232
+ }
233
+ }
203
234
  else if (endpoint.hasBody) {
204
235
  // For binary-response endpoints, --file is the output path, not a JSON
205
236
  // input file — skip the read-as-body branch.
@@ -295,7 +326,7 @@ export default class PriceLists extends Command {
295
326
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
296
327
  }
297
328
  else if (data) {
298
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
329
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
299
330
  }
300
331
  process.stderr.write('\n');
301
332
  }
@@ -309,6 +340,7 @@ export default class PriceLists extends Command {
309
340
  const binResponse = await apiRequest(endpoint.method, apiPath, {
310
341
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
311
342
  data,
343
+ contentType: endpoint.bodyMediaType || undefined,
312
344
  contextName: flags.context,
313
345
  binary: true,
314
346
  });
@@ -334,6 +366,7 @@ export default class PriceLists extends Command {
334
366
  const raw = await apiRequest(endpoint.method, apiPath, {
335
367
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
336
368
  data,
369
+ contentType: endpoint.bodyMediaType || undefined,
337
370
  contextName: flags.context,
338
371
  binary: true,
339
372
  });
@@ -354,6 +387,7 @@ export default class PriceLists extends Command {
354
387
  response = await apiRequest(endpoint.method, apiPath, {
355
388
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
356
389
  data,
390
+ contentType: endpoint.bodyMediaType || undefined,
357
391
  contextName: flags.context,
358
392
  acceptAny: true,
359
393
  });
@@ -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,10 +12,10 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
16
- 'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
17
- 'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'], bodyMediaType: '', specParams: [] },
17
+ 'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'], bodyMediaType: '', specParams: [] },
18
+ 'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
19
  };
19
20
  export default class ProcessorMappings extends Command {
20
21
  static description = 'Manage processor mappings';
@@ -35,7 +36,7 @@ export default class ProcessorMappings extends Command {
35
36
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
36
37
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
37
38
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
38
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
39
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
39
40
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
40
41
  page: Flags.string({ description: 'Page number' }),
41
42
  limit: Flags.string({ description: 'Items per page' }),
@@ -166,6 +167,17 @@ export default class ProcessorMappings extends Command {
166
167
  }
167
168
  }
168
169
  }
170
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
171
+ // have them: their body is not JSON, so --set cannot carry a value there.
172
+ for (const param of endpoint.specParams) {
173
+ const value = flags[param.flag];
174
+ if (value === undefined || value === '') {
175
+ if (param.required)
176
+ this.error(`Missing required flag: --${param.flag}`);
177
+ continue;
178
+ }
179
+ queryParams[param.name] = value;
180
+ }
169
181
  let data;
170
182
  if (endpoint.isMultipart) {
171
183
  if (!flags.file)
@@ -186,6 +198,25 @@ export default class ProcessorMappings extends Command {
186
198
  }
187
199
  data = form;
188
200
  }
201
+ else if (endpoint.bodyMediaType) {
202
+ // The body is not JSON (today only application/x-ndjson): it is sent
203
+ // verbatim, under the content type the endpoint asks for.
204
+ if (flags.file) {
205
+ const fsModule = await import('node:fs');
206
+ try {
207
+ data = fsModule.readFileSync(flags.file, 'utf-8');
208
+ }
209
+ catch {
210
+ this.error(`Failed to read file: ${flags.file}`);
211
+ }
212
+ }
213
+ else if (flags.data) {
214
+ data = flags.data;
215
+ }
216
+ else {
217
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
218
+ }
219
+ }
189
220
  else if (endpoint.hasBody) {
190
221
  // For binary-response endpoints, --file is the output path, not a JSON
191
222
  // input file — skip the read-as-body branch.
@@ -281,7 +312,7 @@ export default class ProcessorMappings extends Command {
281
312
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
282
313
  }
283
314
  else if (data) {
284
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
315
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
285
316
  }
286
317
  process.stderr.write('\n');
287
318
  }
@@ -295,6 +326,7 @@ export default class ProcessorMappings extends Command {
295
326
  const binResponse = await apiRequest(endpoint.method, apiPath, {
296
327
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
297
328
  data,
329
+ contentType: endpoint.bodyMediaType || undefined,
298
330
  contextName: flags.context,
299
331
  binary: true,
300
332
  });
@@ -320,6 +352,7 @@ export default class ProcessorMappings extends Command {
320
352
  const raw = await apiRequest(endpoint.method, apiPath, {
321
353
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
322
354
  data,
355
+ contentType: endpoint.bodyMediaType || undefined,
323
356
  contextName: flags.context,
324
357
  binary: true,
325
358
  });
@@ -340,6 +373,7 @@ export default class ProcessorMappings extends Command {
340
373
  response = await apiRequest(endpoint.method, apiPath, {
341
374
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
342
375
  data,
376
+ contentType: endpoint.bodyMediaType || undefined,
343
377
  contextName: flags.context,
344
378
  acceptAny: true,
345
379
  });