@aderis-erp/cli 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/commands/access-groups.js +42 -8
  2. package/dist/commands/account-transaction-allocations.js +38 -4
  3. package/dist/commands/account-transactions.d.ts +2 -0
  4. package/dist/commands/account-transactions.js +68 -32
  5. package/dist/commands/accounts.js +46 -12
  6. package/dist/commands/agent-platform.js +37 -3
  7. package/dist/commands/agent-runs.js +49 -15
  8. package/dist/commands/analytics.js +39 -5
  9. package/dist/commands/api-clients.js +47 -13
  10. package/dist/commands/api-keys.js +40 -6
  11. package/dist/commands/async-jobs.js +45 -11
  12. package/dist/commands/audit-log.js +38 -4
  13. package/dist/commands/bank-accounts.js +54 -20
  14. package/dist/commands/bank-integrations.js +56 -22
  15. package/dist/commands/business-partners.js +77 -43
  16. package/dist/commands/chat.js +43 -9
  17. package/dist/commands/credit-card-accounts.js +57 -23
  18. package/dist/commands/currencies.js +37 -3
  19. package/dist/commands/custody-accounts.js +41 -7
  20. package/dist/commands/dashboards.js +52 -18
  21. package/dist/commands/delivery-notes.js +69 -35
  22. package/dist/commands/document-checksums.d.ts +1 -0
  23. package/dist/commands/document-checksums.js +38 -3
  24. package/dist/commands/dunning-configs.js +41 -7
  25. package/dist/commands/dunning-reminders.js +44 -10
  26. package/dist/commands/dunning-runs.js +37 -3
  27. package/dist/commands/email-transactions.js +39 -5
  28. package/dist/commands/employees.js +77 -43
  29. package/dist/commands/entity-configurations.js +39 -5
  30. package/dist/commands/event-mapping-rules.js +37 -3
  31. package/dist/commands/event-processor-mappings.js +41 -7
  32. package/dist/commands/files.js +48 -14
  33. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  34. package/dist/commands/financial-instrument-transactions.js +1369 -0
  35. package/dist/commands/financial-instrument-types.js +41 -7
  36. package/dist/commands/financial-instruments.js +41 -7
  37. package/dist/commands/health.js +41 -7
  38. package/dist/commands/imports.js +39 -5
  39. package/dist/commands/inbox.js +42 -8
  40. package/dist/commands/integrations.js +39 -5
  41. package/dist/commands/item-categories.js +41 -7
  42. package/dist/commands/items.js +42 -8
  43. package/dist/commands/journal-lines.js +37 -3
  44. package/dist/commands/journals.js +61 -27
  45. package/dist/commands/kpis.js +44 -10
  46. package/dist/commands/ledgers.js +83 -49
  47. package/dist/commands/my-account.js +54 -20
  48. package/dist/commands/notifications.js +37 -3
  49. package/dist/commands/office-locations.js +41 -7
  50. package/dist/commands/payment-methods.js +46 -12
  51. package/dist/commands/payment-runs.js +54 -20
  52. package/dist/commands/payment-terms.js +42 -8
  53. package/dist/commands/payments.js +43 -9
  54. package/dist/commands/payroll-runs.js +71 -37
  55. package/dist/commands/permissions.js +37 -3
  56. package/dist/commands/price-lists.js +48 -14
  57. package/dist/commands/processor-mappings.js +40 -6
  58. package/dist/commands/processors.js +46 -12
  59. package/dist/commands/purchase-order-types.js +41 -7
  60. package/dist/commands/purchase-orders.js +60 -26
  61. package/dist/commands/reports.js +43 -8
  62. package/dist/commands/roles.js +40 -6
  63. package/dist/commands/salary-types.js +41 -7
  64. package/dist/commands/sales-invoices.js +85 -51
  65. package/dist/commands/sales-orders.js +69 -35
  66. package/dist/commands/saved-views.js +39 -5
  67. package/dist/commands/scheduled-processor-mappings.js +41 -7
  68. package/dist/commands/schedules.js +45 -11
  69. package/dist/commands/settings.js +38 -4
  70. package/dist/commands/tags.js +42 -8
  71. package/dist/commands/tax-codes.js +44 -10
  72. package/dist/commands/tax-matrix.js +41 -7
  73. package/dist/commands/templates.js +49 -15
  74. package/dist/commands/tenants.js +44 -10
  75. package/dist/commands/tools.js +38 -4
  76. package/dist/commands/users.js +58 -24
  77. package/dist/commands/vat-declarations.js +61 -27
  78. package/dist/commands/vendor-invoices.js +81 -47
  79. package/dist/commands/webhooks.js +43 -9
  80. package/dist/commands/work-summary.js +37 -3
  81. package/dist/lib/paths.d.ts +8 -0
  82. package/dist/lib/paths.js +16 -0
  83. package/dist/lib/production-guard.js +2 -0
  84. package/oclif.manifest.json +199 -2
  85. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,7 +12,7 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["validate"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'validate': { method: 'POST', path: '/v1/document-checksums/validate', summary: 'Validate documents against a checksum log', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'validate': { method: 'POST', path: '/v1/document-checksums/validate', summary: 'Validate documents against a checksum log', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: 'application/x-ndjson', specParams: [{ "flag": "percentage", "name": "percentage", "required": true }] },
15
16
  };
16
17
  export default class DocumentChecksums extends Command {
17
18
  static description = 'Manage document checksums';
@@ -26,8 +27,9 @@ export default class DocumentChecksums extends Command {
26
27
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
27
28
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
28
29
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
29
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
30
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
30
31
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
32
+ 'percentage': Flags.string({ description: 'Required. Share of the documents in the log to check, in percent: more than 0, at most 100. The run checks ⌈documents in the log × percentage ÷ 100⌉ documents, at least one, drawn at random with every docume...' }),
31
33
  page: Flags.string({ description: 'Page number' }),
32
34
  limit: Flags.string({ description: 'Items per page' }),
33
35
  include: Flags.string({ description: 'Relations to include' }),
@@ -157,6 +159,17 @@ export default class DocumentChecksums extends Command {
157
159
  }
158
160
  }
159
161
  }
162
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
163
+ // have them: their body is not JSON, so --set cannot carry a value there.
164
+ for (const param of endpoint.specParams) {
165
+ const value = flags[param.flag];
166
+ if (value === undefined || value === '') {
167
+ if (param.required)
168
+ this.error(`Missing required flag: --${param.flag}`);
169
+ continue;
170
+ }
171
+ queryParams[param.name] = value;
172
+ }
160
173
  let data;
161
174
  if (endpoint.isMultipart) {
162
175
  if (!flags.file)
@@ -177,6 +190,25 @@ export default class DocumentChecksums extends Command {
177
190
  }
178
191
  data = form;
179
192
  }
193
+ else if (endpoint.bodyMediaType) {
194
+ // The body is not JSON (today only application/x-ndjson): it is sent
195
+ // verbatim, under the content type the endpoint asks for.
196
+ if (flags.file) {
197
+ const fsModule = await import('node:fs');
198
+ try {
199
+ data = fsModule.readFileSync(flags.file, 'utf-8');
200
+ }
201
+ catch {
202
+ this.error(`Failed to read file: ${flags.file}`);
203
+ }
204
+ }
205
+ else if (flags.data) {
206
+ data = flags.data;
207
+ }
208
+ else {
209
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
210
+ }
211
+ }
180
212
  else if (endpoint.hasBody) {
181
213
  // For binary-response endpoints, --file is the output path, not a JSON
182
214
  // input file — skip the read-as-body branch.
@@ -272,7 +304,7 @@ export default class DocumentChecksums extends Command {
272
304
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
273
305
  }
274
306
  else if (data) {
275
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
307
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
276
308
  }
277
309
  process.stderr.write('\n');
278
310
  }
@@ -286,6 +318,7 @@ export default class DocumentChecksums extends Command {
286
318
  const binResponse = await apiRequest(endpoint.method, apiPath, {
287
319
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
288
320
  data,
321
+ contentType: endpoint.bodyMediaType || undefined,
289
322
  contextName: flags.context,
290
323
  binary: true,
291
324
  });
@@ -311,6 +344,7 @@ export default class DocumentChecksums extends Command {
311
344
  const raw = await apiRequest(endpoint.method, apiPath, {
312
345
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
313
346
  data,
347
+ contentType: endpoint.bodyMediaType || undefined,
314
348
  contextName: flags.context,
315
349
  binary: true,
316
350
  });
@@ -331,6 +365,7 @@ export default class DocumentChecksums extends Command {
331
365
  response = await apiRequest(endpoint.method, apiPath, {
332
366
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
333
367
  data,
368
+ contentType: endpoint.bodyMediaType || undefined,
334
369
  contextName: flags.context,
335
370
  acceptAny: true,
336
371
  });
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,11 +12,11 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
16
- 'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
18
- 'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
20
  };
20
21
  export default class DunningConfigs extends Command {
21
22
  static description = 'Manage dunning configs';
@@ -37,7 +38,7 @@ export default class DunningConfigs extends Command {
37
38
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
38
39
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
39
40
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
40
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
41
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
41
42
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
42
43
  page: Flags.string({ description: 'Page number' }),
43
44
  limit: Flags.string({ description: 'Items per page' }),
@@ -168,6 +169,17 @@ export default class DunningConfigs extends Command {
168
169
  }
169
170
  }
170
171
  }
172
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
173
+ // have them: their body is not JSON, so --set cannot carry a value there.
174
+ for (const param of endpoint.specParams) {
175
+ const value = flags[param.flag];
176
+ if (value === undefined || value === '') {
177
+ if (param.required)
178
+ this.error(`Missing required flag: --${param.flag}`);
179
+ continue;
180
+ }
181
+ queryParams[param.name] = value;
182
+ }
171
183
  let data;
172
184
  if (endpoint.isMultipart) {
173
185
  if (!flags.file)
@@ -188,6 +200,25 @@ export default class DunningConfigs extends Command {
188
200
  }
189
201
  data = form;
190
202
  }
203
+ else if (endpoint.bodyMediaType) {
204
+ // The body is not JSON (today only application/x-ndjson): it is sent
205
+ // verbatim, under the content type the endpoint asks for.
206
+ if (flags.file) {
207
+ const fsModule = await import('node:fs');
208
+ try {
209
+ data = fsModule.readFileSync(flags.file, 'utf-8');
210
+ }
211
+ catch {
212
+ this.error(`Failed to read file: ${flags.file}`);
213
+ }
214
+ }
215
+ else if (flags.data) {
216
+ data = flags.data;
217
+ }
218
+ else {
219
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
220
+ }
221
+ }
191
222
  else if (endpoint.hasBody) {
192
223
  // For binary-response endpoints, --file is the output path, not a JSON
193
224
  // input file — skip the read-as-body branch.
@@ -283,7 +314,7 @@ export default class DunningConfigs extends Command {
283
314
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
284
315
  }
285
316
  else if (data) {
286
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
317
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
287
318
  }
288
319
  process.stderr.write('\n');
289
320
  }
@@ -297,6 +328,7 @@ export default class DunningConfigs extends Command {
297
328
  const binResponse = await apiRequest(endpoint.method, apiPath, {
298
329
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
299
330
  data,
331
+ contentType: endpoint.bodyMediaType || undefined,
300
332
  contextName: flags.context,
301
333
  binary: true,
302
334
  });
@@ -322,6 +354,7 @@ export default class DunningConfigs extends Command {
322
354
  const raw = await apiRequest(endpoint.method, apiPath, {
323
355
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
324
356
  data,
357
+ contentType: endpoint.bodyMediaType || undefined,
325
358
  contextName: flags.context,
326
359
  binary: true,
327
360
  });
@@ -342,6 +375,7 @@ export default class DunningConfigs extends Command {
342
375
  response = await apiRequest(endpoint.method, apiPath, {
343
376
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
344
377
  data,
378
+ contentType: endpoint.bodyMediaType || undefined,
345
379
  contextName: flags.context,
346
380
  acceptAny: true,
347
381
  });
@@ -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,14 +12,14 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["pending", "ongoing", "cancelled", "get", "update", "list", "approve", "cancel"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'pending': { method: 'GET', path: '/v1/dunning-reminders/pending', summary: 'Get pending reminders (awaiting approval) with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'ongoing': { method: 'GET', path: '/v1/dunning-reminders/ongoing', summary: 'Get ongoing reminders (pending, sent, or failed) with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'cancelled': { method: 'GET', path: '/v1/dunning-reminders/cancelled', summary: 'Get cancelled reminders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'get': { method: 'GET', path: '/v1/dunning-reminders/{id}', summary: 'Get a single dunning reminder by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'update': { method: 'PATCH', path: '/v1/dunning-reminders/{id}', summary: 'Update recipient or action on a dunning reminder', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'list': { method: 'GET', path: '/v1/dunning-reminders', summary: 'Get all dunning reminders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'approve': { method: 'POST', path: '/v1/dunning-reminders/{reminderId}/approve', summary: 'Approve a dunning reminder for sending', pathParams: ['reminderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'cancel': { method: 'POST', path: '/v1/dunning-reminders/{reminderId}/cancel', summary: 'Cancel a dunning reminder', pathParams: ['reminderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'pending': { method: 'GET', path: '/v1/dunning-reminders/pending', summary: 'Get pending reminders (awaiting approval) with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'ongoing': { method: 'GET', path: '/v1/dunning-reminders/ongoing', summary: 'Get ongoing reminders (pending, sent, or failed) with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'cancelled': { method: 'GET', path: '/v1/dunning-reminders/cancelled', summary: 'Get cancelled reminders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'get': { method: 'GET', path: '/v1/dunning-reminders/{id}', summary: 'Get a single dunning reminder by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'update': { method: 'PATCH', path: '/v1/dunning-reminders/{id}', summary: 'Update recipient or action on a dunning reminder', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'list': { method: 'GET', path: '/v1/dunning-reminders', summary: 'Get all dunning reminders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'approve': { method: 'POST', path: '/v1/dunning-reminders/{reminderId}/approve', summary: 'Approve a dunning reminder for sending', pathParams: ['reminderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'cancel': { method: 'POST', path: '/v1/dunning-reminders/{reminderId}/cancel', summary: 'Cancel a dunning reminder', pathParams: ['reminderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
23
  };
23
24
  export default class DunningReminders extends Command {
24
25
  static description = 'Manage dunning reminders';
@@ -38,7 +39,7 @@ export default class DunningReminders extends Command {
38
39
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
39
40
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
40
41
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
41
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
42
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
42
43
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
43
44
  page: Flags.string({ description: 'Page number' }),
44
45
  limit: Flags.string({ description: 'Items per page' }),
@@ -169,6 +170,17 @@ export default class DunningReminders extends Command {
169
170
  }
170
171
  }
171
172
  }
173
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
174
+ // have them: their body is not JSON, so --set cannot carry a value there.
175
+ for (const param of endpoint.specParams) {
176
+ const value = flags[param.flag];
177
+ if (value === undefined || value === '') {
178
+ if (param.required)
179
+ this.error(`Missing required flag: --${param.flag}`);
180
+ continue;
181
+ }
182
+ queryParams[param.name] = value;
183
+ }
172
184
  let data;
173
185
  if (endpoint.isMultipart) {
174
186
  if (!flags.file)
@@ -189,6 +201,25 @@ export default class DunningReminders extends Command {
189
201
  }
190
202
  data = form;
191
203
  }
204
+ else if (endpoint.bodyMediaType) {
205
+ // The body is not JSON (today only application/x-ndjson): it is sent
206
+ // verbatim, under the content type the endpoint asks for.
207
+ if (flags.file) {
208
+ const fsModule = await import('node:fs');
209
+ try {
210
+ data = fsModule.readFileSync(flags.file, 'utf-8');
211
+ }
212
+ catch {
213
+ this.error(`Failed to read file: ${flags.file}`);
214
+ }
215
+ }
216
+ else if (flags.data) {
217
+ data = flags.data;
218
+ }
219
+ else {
220
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
221
+ }
222
+ }
192
223
  else if (endpoint.hasBody) {
193
224
  // For binary-response endpoints, --file is the output path, not a JSON
194
225
  // input file — skip the read-as-body branch.
@@ -284,7 +315,7 @@ export default class DunningReminders extends Command {
284
315
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
285
316
  }
286
317
  else if (data) {
287
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
318
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
288
319
  }
289
320
  process.stderr.write('\n');
290
321
  }
@@ -298,6 +329,7 @@ export default class DunningReminders extends Command {
298
329
  const binResponse = await apiRequest(endpoint.method, apiPath, {
299
330
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
300
331
  data,
332
+ contentType: endpoint.bodyMediaType || undefined,
301
333
  contextName: flags.context,
302
334
  binary: true,
303
335
  });
@@ -323,6 +355,7 @@ export default class DunningReminders extends Command {
323
355
  const raw = await apiRequest(endpoint.method, apiPath, {
324
356
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
325
357
  data,
358
+ contentType: endpoint.bodyMediaType || undefined,
326
359
  contextName: flags.context,
327
360
  binary: true,
328
361
  });
@@ -343,6 +376,7 @@ export default class DunningReminders extends Command {
343
376
  response = await apiRequest(endpoint.method, apiPath, {
344
377
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
345
378
  data,
379
+ contentType: endpoint.bodyMediaType || undefined,
346
380
  contextName: flags.context,
347
381
  acceptAny: true,
348
382
  });
@@ -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 = [...["trigger"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'trigger': { method: 'POST', path: '/v1/dunning-runs/trigger', summary: 'Manually trigger a dunning run', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'trigger': { method: 'POST', path: '/v1/dunning-runs/trigger', summary: 'Manually trigger a dunning run', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
15
16
  };
16
17
  export default class DunningRuns extends Command {
17
18
  static description = 'Manage dunning runs';
@@ -26,7 +27,7 @@ export default class DunningRuns extends Command {
26
27
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
27
28
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
28
29
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
29
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
30
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
30
31
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
31
32
  page: Flags.string({ description: 'Page number' }),
32
33
  limit: Flags.string({ description: 'Items per page' }),
@@ -157,6 +158,17 @@ export default class DunningRuns extends Command {
157
158
  }
158
159
  }
159
160
  }
161
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
162
+ // have them: their body is not JSON, so --set cannot carry a value there.
163
+ for (const param of endpoint.specParams) {
164
+ const value = flags[param.flag];
165
+ if (value === undefined || value === '') {
166
+ if (param.required)
167
+ this.error(`Missing required flag: --${param.flag}`);
168
+ continue;
169
+ }
170
+ queryParams[param.name] = value;
171
+ }
160
172
  let data;
161
173
  if (endpoint.isMultipart) {
162
174
  if (!flags.file)
@@ -177,6 +189,25 @@ export default class DunningRuns extends Command {
177
189
  }
178
190
  data = form;
179
191
  }
192
+ else if (endpoint.bodyMediaType) {
193
+ // The body is not JSON (today only application/x-ndjson): it is sent
194
+ // verbatim, under the content type the endpoint asks for.
195
+ if (flags.file) {
196
+ const fsModule = await import('node:fs');
197
+ try {
198
+ data = fsModule.readFileSync(flags.file, 'utf-8');
199
+ }
200
+ catch {
201
+ this.error(`Failed to read file: ${flags.file}`);
202
+ }
203
+ }
204
+ else if (flags.data) {
205
+ data = flags.data;
206
+ }
207
+ else {
208
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
209
+ }
210
+ }
180
211
  else if (endpoint.hasBody) {
181
212
  // For binary-response endpoints, --file is the output path, not a JSON
182
213
  // input file — skip the read-as-body branch.
@@ -272,7 +303,7 @@ export default class DunningRuns extends Command {
272
303
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
273
304
  }
274
305
  else if (data) {
275
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
306
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
276
307
  }
277
308
  process.stderr.write('\n');
278
309
  }
@@ -286,6 +317,7 @@ export default class DunningRuns extends Command {
286
317
  const binResponse = await apiRequest(endpoint.method, apiPath, {
287
318
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
288
319
  data,
320
+ contentType: endpoint.bodyMediaType || undefined,
289
321
  contextName: flags.context,
290
322
  binary: true,
291
323
  });
@@ -311,6 +343,7 @@ export default class DunningRuns extends Command {
311
343
  const raw = await apiRequest(endpoint.method, apiPath, {
312
344
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
313
345
  data,
346
+ contentType: endpoint.bodyMediaType || undefined,
314
347
  contextName: flags.context,
315
348
  binary: true,
316
349
  });
@@ -331,6 +364,7 @@ export default class DunningRuns extends Command {
331
364
  response = await apiRequest(endpoint.method, apiPath, {
332
365
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
333
366
  data,
367
+ contentType: endpoint.bodyMediaType || undefined,
334
368
  contextName: flags.context,
335
369
  acceptAny: true,
336
370
  });
@@ -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,9 +12,9 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["event-webhook", "list", "get"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'event-webhook': { method: 'POST', path: '/v1/email-transactions/event-webhook', summary: 'SendGrid delivery-event webhook (ECDSA signature-verified, no bearer token)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'list': { method: 'GET', path: '/v1/email-transactions', summary: 'List email transactions for the current tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'get': { method: 'GET', path: '/v1/email-transactions/{id}', summary: 'Get a single email transaction (404 if not in this tenant)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'event-webhook': { method: 'POST', path: '/v1/email-transactions/event-webhook', summary: 'SendGrid delivery-event webhook (ECDSA signature-verified, no bearer token)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'list': { method: 'GET', path: '/v1/email-transactions', summary: 'List email transactions for the current tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/email-transactions/{id}', summary: 'Get a single email transaction (404 if not in this tenant)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
18
  };
18
19
  export default class EmailTransactions extends Command {
19
20
  static description = 'Manage email transactions';
@@ -32,7 +33,7 @@ export default class EmailTransactions extends Command {
32
33
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
33
34
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
34
35
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
35
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
36
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
36
37
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
37
38
  page: Flags.string({ description: 'Page number' }),
38
39
  limit: Flags.string({ description: 'Items per page' }),
@@ -163,6 +164,17 @@ export default class EmailTransactions extends Command {
163
164
  }
164
165
  }
165
166
  }
167
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
168
+ // have them: their body is not JSON, so --set cannot carry a value there.
169
+ for (const param of endpoint.specParams) {
170
+ const value = flags[param.flag];
171
+ if (value === undefined || value === '') {
172
+ if (param.required)
173
+ this.error(`Missing required flag: --${param.flag}`);
174
+ continue;
175
+ }
176
+ queryParams[param.name] = value;
177
+ }
166
178
  let data;
167
179
  if (endpoint.isMultipart) {
168
180
  if (!flags.file)
@@ -183,6 +195,25 @@ export default class EmailTransactions extends Command {
183
195
  }
184
196
  data = form;
185
197
  }
198
+ else if (endpoint.bodyMediaType) {
199
+ // The body is not JSON (today only application/x-ndjson): it is sent
200
+ // verbatim, under the content type the endpoint asks for.
201
+ if (flags.file) {
202
+ const fsModule = await import('node:fs');
203
+ try {
204
+ data = fsModule.readFileSync(flags.file, 'utf-8');
205
+ }
206
+ catch {
207
+ this.error(`Failed to read file: ${flags.file}`);
208
+ }
209
+ }
210
+ else if (flags.data) {
211
+ data = flags.data;
212
+ }
213
+ else {
214
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
215
+ }
216
+ }
186
217
  else if (endpoint.hasBody) {
187
218
  // For binary-response endpoints, --file is the output path, not a JSON
188
219
  // input file — skip the read-as-body branch.
@@ -278,7 +309,7 @@ export default class EmailTransactions extends Command {
278
309
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
279
310
  }
280
311
  else if (data) {
281
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
312
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
282
313
  }
283
314
  process.stderr.write('\n');
284
315
  }
@@ -292,6 +323,7 @@ export default class EmailTransactions extends Command {
292
323
  const binResponse = await apiRequest(endpoint.method, apiPath, {
293
324
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
294
325
  data,
326
+ contentType: endpoint.bodyMediaType || undefined,
295
327
  contextName: flags.context,
296
328
  binary: true,
297
329
  });
@@ -317,6 +349,7 @@ export default class EmailTransactions extends Command {
317
349
  const raw = await apiRequest(endpoint.method, apiPath, {
318
350
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
319
351
  data,
352
+ contentType: endpoint.bodyMediaType || undefined,
320
353
  contextName: flags.context,
321
354
  binary: true,
322
355
  });
@@ -337,6 +370,7 @@ export default class EmailTransactions extends Command {
337
370
  response = await apiRequest(endpoint.method, apiPath, {
338
371
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
339
372
  data,
373
+ contentType: endpoint.bodyMediaType || undefined,
340
374
  contextName: flags.context,
341
375
  acceptAny: true,
342
376
  });