@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
package/README.md CHANGED
@@ -8,6 +8,7 @@ Command-line interface for Aderis Finance ERP. Manage your Aderis account, execu
8
8
  - **Multi-Environment Support** - Switch between dev, test, staging and production environments
9
9
  - **Multi-Client Support** - Manage multiple clients with named contexts
10
10
  - **Multi-Tenant Support** - Switch between tenants within a single login
11
+ - **Production Guard for Claude Code** - From an `@aderis.com` address, only a Claude account in the Aderis organisation can reach `live`
11
12
  - **Full API Access** - Execute all Aderis API endpoints from the command line
12
13
  - **Auto-Generated Commands** - Commands generated directly from OpenAPI spec
13
14
 
@@ -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,12 +12,12 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "list-summaries", "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/access-groups', summary: 'List access groups (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/access-groups', summary: 'Create a new access group', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['types', 'userIds'] },
16
- 'list-summaries': { method: 'GET', path: '/v1/access-groups/summaries', summary: 'List access groups without members (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'get': { method: 'GET', path: '/v1/access-groups/{id}', summary: 'Get a single access group by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'update': { method: 'PATCH', path: '/v1/access-groups/{id}', summary: 'Update an existing access group', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['types', 'userIds'] },
19
- 'delete': { method: 'DELETE', path: '/v1/access-groups/{id}', summary: 'Delete an access group', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/access-groups', summary: 'List access groups (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/access-groups', summary: 'Create a new access group', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['types', 'userIds'], bodyMediaType: '', specParams: [] },
17
+ 'list-summaries': { method: 'GET', path: '/v1/access-groups/summaries', summary: 'List access groups without members (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'get': { method: 'GET', path: '/v1/access-groups/{id}', summary: 'Get a single access group by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'update': { method: 'PATCH', path: '/v1/access-groups/{id}', summary: 'Update an existing access group', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['types', 'userIds'], bodyMediaType: '', specParams: [] },
20
+ 'delete': { method: 'DELETE', path: '/v1/access-groups/{id}', summary: 'Delete an access group', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
21
  };
21
22
  export default class AccessGroups extends Command {
22
23
  static description = 'Manage access groups';
@@ -38,7 +39,7 @@ export default class AccessGroups 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 AccessGroups 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 AccessGroups 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 AccessGroups 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 AccessGroups 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 AccessGroups 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 AccessGroups 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,8 +12,8 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "get"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/account-transaction-allocations', summary: 'List account transaction allocations with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'get': { method: 'GET', path: '/v1/account-transaction-allocations/{id}', summary: 'Get an account transaction allocation by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/account-transaction-allocations', summary: 'List account transaction allocations with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'get': { method: 'GET', path: '/v1/account-transaction-allocations/{id}', summary: 'Get an account transaction allocation by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
17
  };
17
18
  export default class AccountTransactionAllocations extends Command {
18
19
  static description = 'Manage account transaction allocations';
@@ -31,7 +32,7 @@ export default class AccountTransactionAllocations extends Command {
31
32
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
32
33
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
33
34
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
34
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
35
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
35
36
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
36
37
  page: Flags.string({ description: 'Page number' }),
37
38
  limit: Flags.string({ description: 'Items per page' }),
@@ -162,6 +163,17 @@ export default class AccountTransactionAllocations extends Command {
162
163
  }
163
164
  }
164
165
  }
166
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
167
+ // have them: their body is not JSON, so --set cannot carry a value there.
168
+ for (const param of endpoint.specParams) {
169
+ const value = flags[param.flag];
170
+ if (value === undefined || value === '') {
171
+ if (param.required)
172
+ this.error(`Missing required flag: --${param.flag}`);
173
+ continue;
174
+ }
175
+ queryParams[param.name] = value;
176
+ }
165
177
  let data;
166
178
  if (endpoint.isMultipart) {
167
179
  if (!flags.file)
@@ -182,6 +194,25 @@ export default class AccountTransactionAllocations extends Command {
182
194
  }
183
195
  data = form;
184
196
  }
197
+ else if (endpoint.bodyMediaType) {
198
+ // The body is not JSON (today only application/x-ndjson): it is sent
199
+ // verbatim, under the content type the endpoint asks for.
200
+ if (flags.file) {
201
+ const fsModule = await import('node:fs');
202
+ try {
203
+ data = fsModule.readFileSync(flags.file, 'utf-8');
204
+ }
205
+ catch {
206
+ this.error(`Failed to read file: ${flags.file}`);
207
+ }
208
+ }
209
+ else if (flags.data) {
210
+ data = flags.data;
211
+ }
212
+ else {
213
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
214
+ }
215
+ }
185
216
  else if (endpoint.hasBody) {
186
217
  // For binary-response endpoints, --file is the output path, not a JSON
187
218
  // input file — skip the read-as-body branch.
@@ -277,7 +308,7 @@ export default class AccountTransactionAllocations extends Command {
277
308
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
278
309
  }
279
310
  else if (data) {
280
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
311
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
281
312
  }
282
313
  process.stderr.write('\n');
283
314
  }
@@ -291,6 +322,7 @@ export default class AccountTransactionAllocations extends Command {
291
322
  const binResponse = await apiRequest(endpoint.method, apiPath, {
292
323
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
293
324
  data,
325
+ contentType: endpoint.bodyMediaType || undefined,
294
326
  contextName: flags.context,
295
327
  binary: true,
296
328
  });
@@ -316,6 +348,7 @@ export default class AccountTransactionAllocations extends Command {
316
348
  const raw = await apiRequest(endpoint.method, apiPath, {
317
349
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
318
350
  data,
351
+ contentType: endpoint.bodyMediaType || undefined,
319
352
  contextName: flags.context,
320
353
  binary: true,
321
354
  });
@@ -336,6 +369,7 @@ export default class AccountTransactionAllocations extends Command {
336
369
  response = await apiRequest(endpoint.method, apiPath, {
337
370
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
338
371
  data,
372
+ contentType: endpoint.bodyMediaType || undefined,
339
373
  contextName: flags.context,
340
374
  acceptAny: true,
341
375
  });
@@ -14,6 +14,8 @@ export default class AccountTransactions extends Command {
14
14
  data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'tenant-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ archive: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
19
  mode: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
20
  reference: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
21
  async: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -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,36 +12,36 @@ const SERVER_IMPORTS = ["import"];
11
12
  const NESTED_UPSERTS = ["allocations", "allocations-notes", "notes"];
12
13
  const SUBCOMMANDS = [...["retry-booking", "recover-booking", "list", "create", "status-counts", "get", "update", "delete", "reconcile", "allocations-reprocess-unbooked", "allocations-unbooked", "list-allocations", "create-allocations", "get-allocations", "update-allocations", "delete-allocations", "list-allocations-settlement-accounts", "allocations-confirm", "import", "import-camt053", "list-allocations-notes", "create-allocations-notes", "get-allocations-notes", "update-allocations-notes", "delete-allocations-notes", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'retry-booking': { method: 'POST', path: '/v1/account-transactions/{transactionId}/retry-booking', summary: 'Retry complete booking of an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'] },
15
- 'recover-booking': { method: 'POST', path: '/v1/account-transactions/{transactionId}/recover-booking', summary: 'Verify existing accounting and book missing entries together', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'] },
16
- 'list': { method: 'GET', path: '/v1/account-transactions', summary: 'Get all account transactions with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'create': { method: 'POST', path: '/v1/account-transactions', summary: 'Create a new account transaction', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'] },
18
- 'status-counts': { method: 'GET', path: '/v1/account-transactions/status-counts', summary: 'Count account transactions per reconciliation status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'get': { method: 'GET', path: '/v1/account-transactions/{id}', summary: 'Get an account transaction by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'update': { method: 'PATCH', path: '/v1/account-transactions/{id}', summary: 'Update an account transaction', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'] },
21
- 'delete': { method: 'DELETE', path: '/v1/account-transactions/{id}', summary: 'Delete an account transaction', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'reconcile': { method: 'POST', path: '/v1/account-transactions/reconcile', summary: 'Send selected transactions to the reconciliation agent', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['transactionIds'] },
23
- 'allocations-reprocess-unbooked': { method: 'POST', path: '/v1/account-transactions/allocations/reprocess-unbooked', summary: 'Internal — re-book or repair allocations stuck at CONFIRMED', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocationIds'] },
24
- 'allocations-unbooked': { method: 'GET', path: '/v1/account-transactions/allocations/unbooked', summary: 'Internal — list allocations stuck at CONFIRMED', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'list-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Get all allocations for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'create-allocations': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Create an allocation for an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
27
- 'get-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Get an allocation by ID', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'update-allocations': { method: 'PATCH', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Update an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
29
- 'delete-allocations': { method: 'DELETE', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Delete an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'list-allocations-settlement-accounts': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/settlement-accounts', summary: 'Which account each ledger would settle the allocated document against', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
31
- 'allocations-confirm': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/confirm', summary: 'Confirm an allocation (transition from REVIEW to CONFIRMED)', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['counterAccounts'] },
32
- 'import': { method: 'POST', path: '/v1/account-transactions/import', summary: 'Import account transactions', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
33
- 'import-camt053': { method: 'POST', path: '/v1/account-transactions/import/camt053', summary: 'Import CAMT.053 bank statement (ISO 20022 format)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'list-allocations-notes': { method: 'GET', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Get all notes for an allocation', pathParams: ['allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
35
- 'create-allocations-notes': { method: 'POST', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Create a note for an allocation', pathParams: ['allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'get-allocations-notes': { method: 'GET', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Get a specific note for an allocation', pathParams: ['allocationId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'update-allocations-notes': { method: 'PATCH', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Update a note for an allocation', pathParams: ['allocationId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
38
- 'delete-allocations-notes': { method: 'DELETE', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Delete a note from an allocation', pathParams: ['allocationId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
39
- 'list-notes': { method: 'GET', path: '/v1/account-transactions/{transactionId}/notes', summary: 'Get all notes for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
40
- 'create-notes': { method: 'POST', path: '/v1/account-transactions/{transactionId}/notes', summary: 'Create a note for an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
- 'get-notes': { method: 'GET', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Get a specific note for an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
- 'update-notes': { method: 'PATCH', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Update a note for an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
- 'delete-notes': { method: 'DELETE', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Delete a note from an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'retry-booking': { method: 'POST', path: '/v1/account-transactions/{transactionId}/retry-booking', summary: 'Retry complete booking of an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'], bodyMediaType: '', specParams: [] },
16
+ 'recover-booking': { method: 'POST', path: '/v1/account-transactions/{transactionId}/recover-booking', summary: 'Verify existing accounting and book missing entries together', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'], bodyMediaType: '', specParams: [] },
17
+ 'list': { method: 'GET', path: '/v1/account-transactions', summary: 'Get all account transactions with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'create': { method: 'POST', path: '/v1/account-transactions', summary: 'Create a new account transaction', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'], bodyMediaType: '', specParams: [] },
19
+ 'status-counts': { method: 'GET', path: '/v1/account-transactions/status-counts', summary: 'Count account transactions per reconciliation status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'get': { method: 'GET', path: '/v1/account-transactions/{id}', summary: 'Get an account transaction by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'update': { method: 'PATCH', path: '/v1/account-transactions/{id}', summary: 'Update an account transaction', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocations'], bodyMediaType: '', specParams: [] },
22
+ 'delete': { method: 'DELETE', path: '/v1/account-transactions/{id}', summary: 'Delete an account transaction', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'reconcile': { method: 'POST', path: '/v1/account-transactions/reconcile', summary: 'Send selected transactions to the reconciliation agent', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['transactionIds'], bodyMediaType: '', specParams: [] },
24
+ 'allocations-reprocess-unbooked': { method: 'POST', path: '/v1/account-transactions/allocations/reprocess-unbooked', summary: 'Internal — re-book or repair allocations stuck at CONFIRMED', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['allocationIds'], bodyMediaType: '', specParams: [] },
25
+ 'allocations-unbooked': { method: 'GET', path: '/v1/account-transactions/allocations/unbooked', summary: 'Internal — list allocations stuck at CONFIRMED', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'list-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Get all allocations for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'create-allocations': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Create an allocation for an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'], bodyMediaType: '', specParams: [] },
28
+ 'get-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Get an allocation by ID', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'update-allocations': { method: 'PATCH', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Update an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'], bodyMediaType: '', specParams: [] },
30
+ 'delete-allocations': { method: 'DELETE', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Delete an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'list-allocations-settlement-accounts': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/settlement-accounts', summary: 'Which account each ledger would settle the allocated document against', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
32
+ 'allocations-confirm': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/confirm', summary: 'Confirm an allocation (transition from REVIEW to CONFIRMED)', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['counterAccounts'], bodyMediaType: '', specParams: [] },
33
+ 'import': { method: 'POST', path: '/v1/account-transactions/import', summary: 'Import account transactions', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
34
+ 'import-camt053': { method: 'POST', path: '/v1/account-transactions/import/camt053', summary: 'Import CAMT.053 bank statement (ISO 20022 format)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: 'application/xml', specParams: [{ "flag": "tenant-id", "name": "tenantId", "required": false }, { "flag": "archive", "name": "archive", "required": false }] },
35
+ 'list-allocations-notes': { method: 'GET', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Get all notes for an allocation', pathParams: ['allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
36
+ 'create-allocations-notes': { method: 'POST', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Create a note for an allocation', pathParams: ['allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'get-allocations-notes': { method: 'GET', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Get a specific note for an allocation', pathParams: ['allocationId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'update-allocations-notes': { method: 'PATCH', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Update a note for an allocation', pathParams: ['allocationId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
+ 'delete-allocations-notes': { method: 'DELETE', path: '/v1/account-transactions/allocations/{allocationId}/notes/{noteId}', summary: 'Delete a note from an allocation', pathParams: ['allocationId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
40
+ 'list-notes': { method: 'GET', path: '/v1/account-transactions/{transactionId}/notes', summary: 'Get all notes for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
41
+ 'create-notes': { method: 'POST', path: '/v1/account-transactions/{transactionId}/notes', summary: 'Create a note for an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
42
+ 'get-notes': { method: 'GET', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Get a specific note for an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
43
+ 'update-notes': { method: 'PATCH', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Update a note for an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
+ 'delete-notes': { method: 'DELETE', path: '/v1/account-transactions/{transactionId}/notes/{noteId}', summary: 'Delete a note from an account transaction', pathParams: ['transactionId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
44
45
  };
45
46
  export default class AccountTransactions extends Command {
46
47
  static description = 'Manage account transactions';
@@ -62,8 +63,10 @@ export default class AccountTransactions extends Command {
62
63
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
63
64
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
64
65
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
65
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
66
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
66
67
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
68
+ 'tenant-id': Flags.string({ description: 'Accepted for backward compatibility; the tenant comes from the caller identity.' }),
69
+ 'archive': Flags.string({ description: 'Set to false to import without storing the raw XML. Absent means the raw statement is stored.' }),
67
70
  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'] }),
68
71
  reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
69
72
  async: Flags.boolean({ description: 'Import in the background: answers with a PENDING run to follow via "aderis imports get <id>"' }),
@@ -199,6 +202,17 @@ export default class AccountTransactions extends Command {
199
202
  }
200
203
  }
201
204
  }
205
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
206
+ // have them: their body is not JSON, so --set cannot carry a value there.
207
+ for (const param of endpoint.specParams) {
208
+ const value = flags[param.flag];
209
+ if (value === undefined || value === '') {
210
+ if (param.required)
211
+ this.error(`Missing required flag: --${param.flag}`);
212
+ continue;
213
+ }
214
+ queryParams[param.name] = value;
215
+ }
202
216
  let data;
203
217
  if (endpoint.isMultipart) {
204
218
  if (!flags.file)
@@ -219,6 +233,25 @@ export default class AccountTransactions extends Command {
219
233
  }
220
234
  data = form;
221
235
  }
236
+ else if (endpoint.bodyMediaType) {
237
+ // The body is not JSON (today only application/x-ndjson): it is sent
238
+ // verbatim, under the content type the endpoint asks for.
239
+ if (flags.file) {
240
+ const fsModule = await import('node:fs');
241
+ try {
242
+ data = fsModule.readFileSync(flags.file, 'utf-8');
243
+ }
244
+ catch {
245
+ this.error(`Failed to read file: ${flags.file}`);
246
+ }
247
+ }
248
+ else if (flags.data) {
249
+ data = flags.data;
250
+ }
251
+ else {
252
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
253
+ }
254
+ }
222
255
  else if (endpoint.hasBody) {
223
256
  // For binary-response endpoints, --file is the output path, not a JSON
224
257
  // input file — skip the read-as-body branch.
@@ -314,7 +347,7 @@ export default class AccountTransactions extends Command {
314
347
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
315
348
  }
316
349
  else if (data) {
317
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
350
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
318
351
  }
319
352
  process.stderr.write('\n');
320
353
  }
@@ -328,6 +361,7 @@ export default class AccountTransactions extends Command {
328
361
  const binResponse = await apiRequest(endpoint.method, apiPath, {
329
362
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
330
363
  data,
364
+ contentType: endpoint.bodyMediaType || undefined,
331
365
  contextName: flags.context,
332
366
  binary: true,
333
367
  });
@@ -353,6 +387,7 @@ export default class AccountTransactions extends Command {
353
387
  const raw = await apiRequest(endpoint.method, apiPath, {
354
388
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
355
389
  data,
390
+ contentType: endpoint.bodyMediaType || undefined,
356
391
  contextName: flags.context,
357
392
  binary: true,
358
393
  });
@@ -373,6 +408,7 @@ export default class AccountTransactions extends Command {
373
408
  response = await apiRequest(endpoint.method, apiPath, {
374
409
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
375
410
  data,
411
+ contentType: endpoint.bodyMediaType || undefined,
376
412
  contextName: flags.context,
377
413
  acceptAny: true,
378
414
  });
@@ -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,16 +12,16 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = ["notes"];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/accounts', summary: 'List all accounts', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/accounts', summary: 'Create a new account', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
16
- 'get': { method: 'GET', path: '/v1/accounts/{id}', summary: 'Get an account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/accounts/{id}', summary: 'Update an account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'] },
18
- 'delete': { method: 'DELETE', path: '/v1/accounts/{id}', summary: 'Delete an account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'list-notes': { method: 'GET', path: '/v1/accounts/{accountId}/notes', summary: 'Get all notes for an account', pathParams: ['accountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'create-notes': { method: 'POST', path: '/v1/accounts/{accountId}/notes', summary: 'Create a note for an account', pathParams: ['accountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'get-notes': { method: 'GET', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Get a specific note for an account', pathParams: ['accountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'update-notes': { method: 'PATCH', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Update a note for an account', pathParams: ['accountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'delete-notes': { method: 'DELETE', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Delete a note from an account', pathParams: ['accountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/accounts', summary: 'List all accounts', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/accounts', summary: 'Create a new account', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/accounts/{id}', summary: 'Get an account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/accounts/{id}', summary: 'Update an account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/accounts/{id}', summary: 'Delete an account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'list-notes': { method: 'GET', path: '/v1/accounts/{accountId}/notes', summary: 'Get all notes for an account', pathParams: ['accountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'create-notes': { method: 'POST', path: '/v1/accounts/{accountId}/notes', summary: 'Create a note for an account', pathParams: ['accountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'get-notes': { method: 'GET', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Get a specific note for an account', pathParams: ['accountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'update-notes': { method: 'PATCH', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Update a note for an account', pathParams: ['accountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'delete-notes': { method: 'DELETE', path: '/v1/accounts/{accountId}/notes/{noteId}', summary: 'Delete a note from an account', pathParams: ['accountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
25
  };
25
26
  export default class Accounts extends Command {
26
27
  static description = 'Manage accounts';
@@ -42,7 +43,7 @@ export default class Accounts extends Command {
42
43
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
43
44
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
44
45
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
45
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
46
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
46
47
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
47
48
  'note-id': Flags.string({ description: 'Path param: noteId' }),
48
49
  page: Flags.string({ description: 'Page number' }),
@@ -174,6 +175,17 @@ export default class Accounts extends Command {
174
175
  }
175
176
  }
176
177
  }
178
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
179
+ // have them: their body is not JSON, so --set cannot carry a value there.
180
+ for (const param of endpoint.specParams) {
181
+ const value = flags[param.flag];
182
+ if (value === undefined || value === '') {
183
+ if (param.required)
184
+ this.error(`Missing required flag: --${param.flag}`);
185
+ continue;
186
+ }
187
+ queryParams[param.name] = value;
188
+ }
177
189
  let data;
178
190
  if (endpoint.isMultipart) {
179
191
  if (!flags.file)
@@ -194,6 +206,25 @@ export default class Accounts extends Command {
194
206
  }
195
207
  data = form;
196
208
  }
209
+ else if (endpoint.bodyMediaType) {
210
+ // The body is not JSON (today only application/x-ndjson): it is sent
211
+ // verbatim, under the content type the endpoint asks for.
212
+ if (flags.file) {
213
+ const fsModule = await import('node:fs');
214
+ try {
215
+ data = fsModule.readFileSync(flags.file, 'utf-8');
216
+ }
217
+ catch {
218
+ this.error(`Failed to read file: ${flags.file}`);
219
+ }
220
+ }
221
+ else if (flags.data) {
222
+ data = flags.data;
223
+ }
224
+ else {
225
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
226
+ }
227
+ }
197
228
  else if (endpoint.hasBody) {
198
229
  // For binary-response endpoints, --file is the output path, not a JSON
199
230
  // input file — skip the read-as-body branch.
@@ -289,7 +320,7 @@ export default class Accounts extends Command {
289
320
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
290
321
  }
291
322
  else if (data) {
292
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
323
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
293
324
  }
294
325
  process.stderr.write('\n');
295
326
  }
@@ -303,6 +334,7 @@ export default class Accounts extends Command {
303
334
  const binResponse = await apiRequest(endpoint.method, apiPath, {
304
335
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
305
336
  data,
337
+ contentType: endpoint.bodyMediaType || undefined,
306
338
  contextName: flags.context,
307
339
  binary: true,
308
340
  });
@@ -328,6 +360,7 @@ export default class Accounts extends Command {
328
360
  const raw = await apiRequest(endpoint.method, apiPath, {
329
361
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
330
362
  data,
363
+ contentType: endpoint.bodyMediaType || undefined,
331
364
  contextName: flags.context,
332
365
  binary: true,
333
366
  });
@@ -348,6 +381,7 @@ export default class Accounts extends Command {
348
381
  response = await apiRequest(endpoint.method, apiPath, {
349
382
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
350
383
  data,
384
+ contentType: endpoint.bodyMediaType || undefined,
351
385
  contextName: flags.context,
352
386
  acceptAny: true,
353
387
  });