@aderis-erp/cli 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/access-groups.js +42 -8
  3. package/dist/commands/account-transaction-allocations.js +38 -4
  4. package/dist/commands/account-transactions.d.ts +2 -0
  5. package/dist/commands/account-transactions.js +68 -32
  6. package/dist/commands/accounts.js +46 -12
  7. package/dist/commands/agent-platform.js +37 -3
  8. package/dist/commands/agent-runs.js +49 -15
  9. package/dist/commands/analytics.js +39 -5
  10. package/dist/commands/api-clients.d.ts +58 -0
  11. package/dist/commands/api-clients.js +1375 -0
  12. package/dist/commands/api-keys.js +40 -6
  13. package/dist/commands/async-jobs.js +45 -11
  14. package/dist/commands/audit-log.js +38 -4
  15. package/dist/commands/bank-accounts.js +54 -20
  16. package/dist/commands/bank-integrations.js +56 -22
  17. package/dist/commands/business-partners.js +77 -43
  18. package/dist/commands/chat.js +43 -9
  19. package/dist/commands/credit-card-accounts.d.ts +4 -0
  20. package/dist/commands/credit-card-accounts.js +64 -25
  21. package/dist/commands/currencies.d.ts +56 -0
  22. package/dist/commands/currencies.js +1361 -0
  23. package/dist/commands/custody-accounts.d.ts +56 -0
  24. package/dist/commands/custody-accounts.js +1369 -0
  25. package/dist/commands/{integration-identities.d.ts → dashboards.d.ts} +3 -3
  26. package/dist/commands/dashboards.js +1381 -0
  27. package/dist/commands/delivery-notes.js +69 -35
  28. package/dist/commands/document-checksums.d.ts +57 -0
  29. package/dist/commands/{integration-identities.js → document-checksums.js} +48 -21
  30. package/dist/commands/dunning-configs.js +41 -7
  31. package/dist/commands/dunning-reminders.js +44 -10
  32. package/dist/commands/dunning-runs.js +37 -3
  33. package/dist/commands/email-transactions.js +39 -5
  34. package/dist/commands/employees.js +77 -43
  35. package/dist/commands/entity-configurations.js +39 -5
  36. package/dist/commands/event-mapping-rules.js +37 -3
  37. package/dist/commands/event-processor-mappings.js +41 -7
  38. package/dist/commands/files.js +48 -14
  39. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  40. package/dist/commands/financial-instrument-transactions.js +1369 -0
  41. package/dist/commands/financial-instrument-types.d.ts +56 -0
  42. package/dist/commands/financial-instrument-types.js +1369 -0
  43. package/dist/commands/financial-instruments.d.ts +56 -0
  44. package/dist/commands/financial-instruments.js +1369 -0
  45. package/dist/commands/health.js +41 -7
  46. package/dist/commands/imports.js +39 -5
  47. package/dist/commands/inbox.js +42 -8
  48. package/dist/commands/integrations.js +39 -5
  49. package/dist/commands/item-categories.js +41 -7
  50. package/dist/commands/items.js +42 -8
  51. package/dist/commands/journal-lines.js +37 -3
  52. package/dist/commands/journals.js +61 -27
  53. package/dist/commands/{workflows.d.ts → kpis.d.ts} +1 -1
  54. package/dist/commands/kpis.js +1371 -0
  55. package/dist/commands/ledgers.js +83 -49
  56. package/dist/commands/my-account.js +54 -20
  57. package/dist/commands/notifications.d.ts +56 -0
  58. package/dist/commands/notifications.js +1360 -0
  59. package/dist/commands/office-locations.js +41 -7
  60. package/dist/commands/payment-methods.js +46 -12
  61. package/dist/commands/payment-runs.js +54 -20
  62. package/dist/commands/payment-terms.js +42 -8
  63. package/dist/commands/payments.js +43 -9
  64. package/dist/commands/payroll-runs.js +71 -37
  65. package/dist/commands/permissions.js +37 -3
  66. package/dist/commands/price-lists.js +48 -14
  67. package/dist/commands/processor-mappings.js +40 -6
  68. package/dist/commands/processors.js +46 -12
  69. package/dist/commands/purchase-order-types.js +41 -7
  70. package/dist/commands/purchase-orders.js +60 -26
  71. package/dist/commands/reports.js +43 -8
  72. package/dist/commands/roles.js +40 -6
  73. package/dist/commands/salary-types.js +41 -7
  74. package/dist/commands/sales-invoices.js +85 -51
  75. package/dist/commands/sales-orders.js +69 -35
  76. package/dist/commands/saved-views.js +39 -5
  77. package/dist/commands/scheduled-processor-mappings.js +41 -7
  78. package/dist/commands/schedules.js +45 -11
  79. package/dist/commands/settings.d.ts +56 -0
  80. package/dist/commands/settings.js +1361 -0
  81. package/dist/commands/tags.js +42 -8
  82. package/dist/commands/tax-codes.js +44 -10
  83. package/dist/commands/tax-matrix.js +41 -7
  84. package/dist/commands/templates.js +50 -15
  85. package/dist/commands/tenants.js +44 -10
  86. package/dist/commands/tools.js +38 -4
  87. package/dist/commands/users.js +58 -24
  88. package/dist/commands/vat-declarations.js +61 -27
  89. package/dist/commands/vendor-invoices.js +82 -47
  90. package/dist/commands/webhooks.js +43 -9
  91. package/dist/commands/work-summary.d.ts +56 -0
  92. package/dist/commands/{workflows.js → work-summary.js} +49 -18
  93. package/dist/lib/auth/index.js +5 -0
  94. package/dist/lib/paths.d.ts +8 -0
  95. package/dist/lib/paths.js +16 -0
  96. package/dist/lib/production-guard.d.ts +42 -0
  97. package/dist/lib/production-guard.js +94 -0
  98. package/oclif.manifest.json +2142 -333
  99. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,9 +12,9 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list-entities", "get-entities", "update-entities"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list-entities': { method: 'GET', path: '/v1/config/entities', summary: 'List all entity-type configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'get-entities': { method: 'GET', path: '/v1/config/entities/{entityType}', summary: 'Get one entity-type config', pathParams: ['entityType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'update-entities': { method: 'PATCH', path: '/v1/config/entities/{entityType}', summary: 'Adjust one entity-type config (admin only)', pathParams: ['entityType'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list-entities': { method: 'GET', path: '/v1/config/entities', summary: 'List all entity-type configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'get-entities': { method: 'GET', path: '/v1/config/entities/{entityType}', summary: 'Get one entity-type config', pathParams: ['entityType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'update-entities': { method: 'PATCH', path: '/v1/config/entities/{entityType}', summary: 'Adjust one entity-type config (admin only)', pathParams: ['entityType'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
18
  };
18
19
  export default class EntityConfigurations extends Command {
19
20
  static description = 'Manage entity configurations';
@@ -28,7 +29,7 @@ export default class EntityConfigurations extends Command {
28
29
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
29
30
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
30
31
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
31
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
32
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
32
33
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
33
34
  page: Flags.string({ description: 'Page number' }),
34
35
  limit: Flags.string({ description: 'Items per page' }),
@@ -159,6 +160,17 @@ export default class EntityConfigurations extends Command {
159
160
  }
160
161
  }
161
162
  }
163
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
164
+ // have them: their body is not JSON, so --set cannot carry a value there.
165
+ for (const param of endpoint.specParams) {
166
+ const value = flags[param.flag];
167
+ if (value === undefined || value === '') {
168
+ if (param.required)
169
+ this.error(`Missing required flag: --${param.flag}`);
170
+ continue;
171
+ }
172
+ queryParams[param.name] = value;
173
+ }
162
174
  let data;
163
175
  if (endpoint.isMultipart) {
164
176
  if (!flags.file)
@@ -179,6 +191,25 @@ export default class EntityConfigurations extends Command {
179
191
  }
180
192
  data = form;
181
193
  }
194
+ else if (endpoint.bodyMediaType) {
195
+ // The body is not JSON (today only application/x-ndjson): it is sent
196
+ // verbatim, under the content type the endpoint asks for.
197
+ if (flags.file) {
198
+ const fsModule = await import('node:fs');
199
+ try {
200
+ data = fsModule.readFileSync(flags.file, 'utf-8');
201
+ }
202
+ catch {
203
+ this.error(`Failed to read file: ${flags.file}`);
204
+ }
205
+ }
206
+ else if (flags.data) {
207
+ data = flags.data;
208
+ }
209
+ else {
210
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
211
+ }
212
+ }
182
213
  else if (endpoint.hasBody) {
183
214
  // For binary-response endpoints, --file is the output path, not a JSON
184
215
  // input file — skip the read-as-body branch.
@@ -274,7 +305,7 @@ export default class EntityConfigurations extends Command {
274
305
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
275
306
  }
276
307
  else if (data) {
277
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
308
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
278
309
  }
279
310
  process.stderr.write('\n');
280
311
  }
@@ -288,6 +319,7 @@ export default class EntityConfigurations extends Command {
288
319
  const binResponse = await apiRequest(endpoint.method, apiPath, {
289
320
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
290
321
  data,
322
+ contentType: endpoint.bodyMediaType || undefined,
291
323
  contextName: flags.context,
292
324
  binary: true,
293
325
  });
@@ -313,6 +345,7 @@ export default class EntityConfigurations extends Command {
313
345
  const raw = await apiRequest(endpoint.method, apiPath, {
314
346
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
315
347
  data,
348
+ contentType: endpoint.bodyMediaType || undefined,
316
349
  contextName: flags.context,
317
350
  binary: true,
318
351
  });
@@ -333,6 +366,7 @@ export default class EntityConfigurations extends Command {
333
366
  response = await apiRequest(endpoint.method, apiPath, {
334
367
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
335
368
  data,
369
+ contentType: endpoint.bodyMediaType || undefined,
336
370
  contextName: flags.context,
337
371
  acceptAny: true,
338
372
  });
@@ -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 = [...["fields"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'fields': { method: 'GET', path: '/v1/event-mapping-rules/fields', summary: 'List the fields an event mapping’s conditions may test', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'fields': { method: 'GET', path: '/v1/event-mapping-rules/fields', summary: 'List the fields an event mapping’s conditions may test', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
15
16
  };
16
17
  export default class EventMappingRules extends Command {
17
18
  static description = 'Manage event mapping rules';
@@ -26,7 +27,7 @@ export default class EventMappingRules 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 EventMappingRules 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 EventMappingRules 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 EventMappingRules 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 EventMappingRules 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 EventMappingRules 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 EventMappingRules 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,11 +12,11 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["events", "list", "create", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'events': { method: 'GET', path: '/v1/event-processor-mappings/events', summary: 'List all valid event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'list': { method: 'GET', path: '/v1/event-processor-mappings', summary: 'List all event type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'create': { method: 'POST', path: '/v1/event-processor-mappings', summary: 'Create event type processor mappings', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['eventTypes', 'includes'] },
17
- 'update': { method: 'PATCH', path: '/v1/event-processor-mappings/{id}', summary: 'Update an event type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['includes'] },
18
- 'delete': { method: 'DELETE', path: '/v1/event-processor-mappings/{id}', summary: 'Delete an event type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'events': { method: 'GET', path: '/v1/event-processor-mappings/events', summary: 'List all valid event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'list': { method: 'GET', path: '/v1/event-processor-mappings', summary: 'List all event type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'create': { method: 'POST', path: '/v1/event-processor-mappings', summary: 'Create event type processor mappings', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['eventTypes', 'includes'], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/event-processor-mappings/{id}', summary: 'Update an event type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['includes'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/event-processor-mappings/{id}', summary: 'Delete an event type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
20
  };
20
21
  export default class EventProcessorMappings extends Command {
21
22
  static description = 'Manage event processor mappings';
@@ -36,7 +37,7 @@ export default class EventProcessorMappings extends Command {
36
37
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
37
38
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
38
39
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
39
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
40
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
40
41
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
41
42
  page: Flags.string({ description: 'Page number' }),
42
43
  limit: Flags.string({ description: 'Items per page' }),
@@ -167,6 +168,17 @@ export default class EventProcessorMappings extends Command {
167
168
  }
168
169
  }
169
170
  }
171
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
172
+ // have them: their body is not JSON, so --set cannot carry a value there.
173
+ for (const param of endpoint.specParams) {
174
+ const value = flags[param.flag];
175
+ if (value === undefined || value === '') {
176
+ if (param.required)
177
+ this.error(`Missing required flag: --${param.flag}`);
178
+ continue;
179
+ }
180
+ queryParams[param.name] = value;
181
+ }
170
182
  let data;
171
183
  if (endpoint.isMultipart) {
172
184
  if (!flags.file)
@@ -187,6 +199,25 @@ export default class EventProcessorMappings extends Command {
187
199
  }
188
200
  data = form;
189
201
  }
202
+ else if (endpoint.bodyMediaType) {
203
+ // The body is not JSON (today only application/x-ndjson): it is sent
204
+ // verbatim, under the content type the endpoint asks for.
205
+ if (flags.file) {
206
+ const fsModule = await import('node:fs');
207
+ try {
208
+ data = fsModule.readFileSync(flags.file, 'utf-8');
209
+ }
210
+ catch {
211
+ this.error(`Failed to read file: ${flags.file}`);
212
+ }
213
+ }
214
+ else if (flags.data) {
215
+ data = flags.data;
216
+ }
217
+ else {
218
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
219
+ }
220
+ }
190
221
  else if (endpoint.hasBody) {
191
222
  // For binary-response endpoints, --file is the output path, not a JSON
192
223
  // input file — skip the read-as-body branch.
@@ -282,7 +313,7 @@ export default class EventProcessorMappings extends Command {
282
313
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
283
314
  }
284
315
  else if (data) {
285
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
316
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
286
317
  }
287
318
  process.stderr.write('\n');
288
319
  }
@@ -296,6 +327,7 @@ export default class EventProcessorMappings extends Command {
296
327
  const binResponse = await apiRequest(endpoint.method, apiPath, {
297
328
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
298
329
  data,
330
+ contentType: endpoint.bodyMediaType || undefined,
299
331
  contextName: flags.context,
300
332
  binary: true,
301
333
  });
@@ -321,6 +353,7 @@ export default class EventProcessorMappings extends Command {
321
353
  const raw = await apiRequest(endpoint.method, apiPath, {
322
354
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
323
355
  data,
356
+ contentType: endpoint.bodyMediaType || undefined,
324
357
  contextName: flags.context,
325
358
  binary: true,
326
359
  });
@@ -341,6 +374,7 @@ export default class EventProcessorMappings extends Command {
341
374
  response = await apiRequest(endpoint.method, apiPath, {
342
375
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
343
376
  data,
377
+ contentType: endpoint.bodyMediaType || undefined,
344
378
  contextName: flags.context,
345
379
  acceptAny: true,
346
380
  });
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,18 +12,18 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "download-all", "download", "get", "references", "update", "discard", "move", "retention", "hold", "reclassify"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
- 'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'download': { method: 'GET', path: '/v1/files/{id}/download', summary: 'Get a signed download URL for a file', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'get': { method: 'GET', path: '/v1/files/{id}', summary: 'Get a file by id (permission-scoped)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'references'] },
21
- 'discard': { method: 'POST', path: '/v1/files/{fileId}/discard', summary: 'Discard a file (sets status DISCARDED; stays readable by id)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'retention': { method: 'POST', path: '/v1/files/{fileId}/retention', summary: 'Set or override retention period on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'hold': { method: 'POST', path: '/v1/files/{fileId}/hold', summary: 'Set or release legal hold on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
25
- 'reclassify': { method: 'POST', path: '/v1/files/{fileId}/reclassify', summary: 'Reclassify a file and reprocess it through the inbox pipeline', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
17
+ 'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'download': { method: 'GET', path: '/v1/files/{id}/download', summary: 'Get a signed download URL for a file', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'get': { method: 'GET', path: '/v1/files/{id}', summary: 'Get a file by id (permission-scoped)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'references'], bodyMediaType: '', specParams: [] },
22
+ 'discard': { method: 'POST', path: '/v1/files/{fileId}/discard', summary: 'Discard a file (sets status DISCARDED; stays readable by id)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'retention': { method: 'POST', path: '/v1/files/{fileId}/retention', summary: 'Set or override retention period on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'hold': { method: 'POST', path: '/v1/files/{fileId}/hold', summary: 'Set or release legal hold on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
+ 'reclassify': { method: 'POST', path: '/v1/files/{fileId}/reclassify', summary: 'Reclassify a file and reprocess it through the inbox pipeline', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
26
27
  };
27
28
  export default class Files extends Command {
28
29
  static description = 'Manage files';
@@ -44,7 +45,7 @@ export default class Files extends Command {
44
45
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
45
46
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
46
47
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
47
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
48
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
48
49
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
49
50
  page: Flags.string({ description: 'Page number' }),
50
51
  limit: Flags.string({ description: 'Items per page' }),
@@ -175,6 +176,17 @@ export default class Files extends Command {
175
176
  }
176
177
  }
177
178
  }
179
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
180
+ // have them: their body is not JSON, so --set cannot carry a value there.
181
+ for (const param of endpoint.specParams) {
182
+ const value = flags[param.flag];
183
+ if (value === undefined || value === '') {
184
+ if (param.required)
185
+ this.error(`Missing required flag: --${param.flag}`);
186
+ continue;
187
+ }
188
+ queryParams[param.name] = value;
189
+ }
178
190
  let data;
179
191
  if (endpoint.isMultipart) {
180
192
  if (!flags.file)
@@ -195,6 +207,25 @@ export default class Files extends Command {
195
207
  }
196
208
  data = form;
197
209
  }
210
+ else if (endpoint.bodyMediaType) {
211
+ // The body is not JSON (today only application/x-ndjson): it is sent
212
+ // verbatim, under the content type the endpoint asks for.
213
+ if (flags.file) {
214
+ const fsModule = await import('node:fs');
215
+ try {
216
+ data = fsModule.readFileSync(flags.file, 'utf-8');
217
+ }
218
+ catch {
219
+ this.error(`Failed to read file: ${flags.file}`);
220
+ }
221
+ }
222
+ else if (flags.data) {
223
+ data = flags.data;
224
+ }
225
+ else {
226
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
227
+ }
228
+ }
198
229
  else if (endpoint.hasBody) {
199
230
  // For binary-response endpoints, --file is the output path, not a JSON
200
231
  // input file — skip the read-as-body branch.
@@ -290,7 +321,7 @@ export default class Files extends Command {
290
321
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
291
322
  }
292
323
  else if (data) {
293
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
324
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
294
325
  }
295
326
  process.stderr.write('\n');
296
327
  }
@@ -304,6 +335,7 @@ export default class Files extends Command {
304
335
  const binResponse = await apiRequest(endpoint.method, apiPath, {
305
336
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
306
337
  data,
338
+ contentType: endpoint.bodyMediaType || undefined,
307
339
  contextName: flags.context,
308
340
  binary: true,
309
341
  });
@@ -329,6 +361,7 @@ export default class Files extends Command {
329
361
  const raw = await apiRequest(endpoint.method, apiPath, {
330
362
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
331
363
  data,
364
+ contentType: endpoint.bodyMediaType || undefined,
332
365
  contextName: flags.context,
333
366
  binary: true,
334
367
  });
@@ -349,6 +382,7 @@ export default class Files extends Command {
349
382
  response = await apiRequest(endpoint.method, apiPath, {
350
383
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
351
384
  data,
385
+ contentType: endpoint.bodyMediaType || undefined,
352
386
  contextName: flags.context,
353
387
  acceptAny: true,
354
388
  });
@@ -0,0 +1,56 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class FinancialInstrumentTransactions extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ };
29
+ run(): Promise<void>;
30
+ private runUpsert;
31
+ /**
32
+ * The API's own import: the whole array goes up in one request and comes
33
+ * back as an import run, which records what happened to every record. A bad
34
+ * record lands in errorLog and the rest are still applied, and the run stays
35
+ * readable afterwards with: aderis imports get <id>.
36
+ */
37
+ private runServerImport;
38
+ /** Renders an import run: its counts, then whatever the server refused. */
39
+ private printImportRun;
40
+ private runExport;
41
+ private runCamt053Import;
42
+ private runNestedUpsert;
43
+ private parseFilterValue;
44
+ /** The page line under a table: which page, how many, and any sums the API returned. */
45
+ private printPageSummary;
46
+ /**
47
+ * The API caps how many records a page may hold. Asking for more and getting
48
+ * fewer looks like "that is everything" unless it is said out loud.
49
+ */
50
+ private warnShortPage;
51
+ private sanitizeOutput;
52
+ private getColumns;
53
+ private getValue;
54
+ private printTable;
55
+ private printCsv;
56
+ }