@aderis-erp/cli 1.0.17 → 1.1.1

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 (105) hide show
  1. package/dist/commands/access-groups.js +49 -17
  2. package/dist/commands/account-transaction-allocations.js +46 -14
  3. package/dist/commands/account-transactions.js +75 -35
  4. package/dist/commands/accounts.js +54 -22
  5. package/dist/commands/agent-platform.js +53 -21
  6. package/dist/commands/{nango-integrations.d.ts → agent-runs.d.ts} +1 -1
  7. package/dist/commands/agent-runs.js +1155 -0
  8. package/dist/commands/analytics.js +47 -15
  9. package/dist/commands/api-keys.js +49 -16
  10. package/dist/commands/{text-templates.d.ts → async-jobs.d.ts} +1 -1
  11. package/dist/commands/async-jobs.js +1158 -0
  12. package/dist/commands/audit-log.js +46 -14
  13. package/dist/commands/bank-accounts.js +63 -30
  14. package/dist/commands/bank-integrations.d.ts +1 -0
  15. package/dist/commands/bank-integrations.js +66 -31
  16. package/dist/commands/business-partners.js +77 -44
  17. package/dist/commands/chat.js +52 -19
  18. package/dist/commands/credit-card-accounts.d.ts +1 -0
  19. package/dist/commands/credit-card-accounts.js +67 -29
  20. package/dist/commands/delivery-notes.js +78 -44
  21. package/dist/commands/dunning-configs.js +49 -17
  22. package/dist/commands/dunning-reminders.js +52 -20
  23. package/dist/commands/dunning-runs.js +45 -13
  24. package/dist/commands/email-transactions.js +47 -15
  25. package/dist/commands/employees.js +76 -44
  26. package/dist/commands/entity-configurations.js +47 -15
  27. package/dist/commands/event-processor-mappings.js +49 -17
  28. package/dist/commands/files.js +56 -24
  29. package/dist/commands/health.js +49 -17
  30. package/dist/commands/{tag-configurations.d.ts → imports.d.ts} +1 -1
  31. package/dist/commands/{tag-configurations.js → imports.js} +59 -31
  32. package/dist/commands/inbox.js +52 -19
  33. package/dist/commands/integration-identities.d.ts +40 -0
  34. package/dist/commands/integration-identities.js +1151 -0
  35. package/dist/commands/integrations.js +47 -15
  36. package/dist/commands/item-categories.js +49 -17
  37. package/dist/commands/items.js +49 -17
  38. package/dist/commands/journal-lines.js +45 -13
  39. package/dist/commands/journals.js +68 -36
  40. package/dist/commands/ledgers.d.ts +3 -1
  41. package/dist/commands/ledgers.js +96 -34
  42. package/dist/commands/my-account.d.ts +39 -0
  43. package/dist/commands/my-account.js +1160 -0
  44. package/dist/commands/office-locations.js +49 -17
  45. package/dist/commands/payment-methods.js +54 -22
  46. package/dist/commands/payment-runs.js +61 -29
  47. package/dist/commands/payment-terms.js +50 -18
  48. package/dist/commands/payments.js +51 -19
  49. package/dist/commands/payroll-runs.d.ts +43 -0
  50. package/dist/commands/payroll-runs.js +1188 -0
  51. package/dist/commands/permissions.d.ts +39 -0
  52. package/dist/commands/{text-templates.js → permissions.js} +54 -30
  53. package/dist/commands/price-lists.js +54 -22
  54. package/dist/commands/processor-mappings.js +48 -16
  55. package/dist/commands/processors.js +55 -24
  56. package/dist/commands/purchase-order-types.js +49 -17
  57. package/dist/commands/purchase-orders.js +69 -36
  58. package/dist/commands/reports.js +49 -17
  59. package/dist/commands/roles.d.ts +39 -0
  60. package/dist/commands/{nango-integrations.js → roles.js} +63 -26
  61. package/dist/commands/salary-types.js +49 -17
  62. package/dist/commands/sales-invoices.js +84 -51
  63. package/dist/commands/sales-orders.js +78 -44
  64. package/dist/commands/saved-views.js +47 -15
  65. package/dist/commands/scheduled-processor-mappings.js +49 -17
  66. package/dist/commands/schedules.js +53 -21
  67. package/dist/commands/support/end.d.ts +9 -0
  68. package/dist/commands/support/end.js +29 -0
  69. package/dist/commands/support/start.d.ts +14 -0
  70. package/dist/commands/support/start.js +50 -0
  71. package/dist/commands/support/status.d.ts +10 -0
  72. package/dist/commands/support/status.js +45 -0
  73. package/dist/commands/support/tenants.d.ts +10 -0
  74. package/dist/commands/support/tenants.js +39 -0
  75. package/dist/commands/tags.js +51 -18
  76. package/dist/commands/tax-codes.js +52 -20
  77. package/dist/commands/tax-matrix.js +49 -17
  78. package/dist/commands/templates.d.ts +39 -0
  79. package/dist/commands/templates.js +1157 -0
  80. package/dist/commands/tenants.js +52 -20
  81. package/dist/commands/tools.js +46 -14
  82. package/dist/commands/users.d.ts +42 -0
  83. package/dist/commands/users.js +1174 -0
  84. package/dist/commands/vat-declarations.d.ts +2 -0
  85. package/dist/commands/vat-declarations.js +72 -36
  86. package/dist/commands/vendor-invoices.js +81 -49
  87. package/dist/commands/webhooks.js +51 -19
  88. package/dist/commands/workflows.js +47 -15
  89. package/dist/lib/api-client.d.ts +6 -0
  90. package/dist/lib/api-client.js +13 -0
  91. package/dist/lib/auth/constants.d.ts +12 -0
  92. package/dist/lib/auth/constants.js +12 -0
  93. package/dist/lib/auth/index.d.ts +11 -1
  94. package/dist/lib/auth/index.js +105 -6
  95. package/dist/lib/auth/jwks.d.ts +19 -0
  96. package/dist/lib/auth/jwks.js +112 -0
  97. package/dist/lib/auth/keycloak-provider.d.ts +45 -7
  98. package/dist/lib/auth/keycloak-provider.js +161 -27
  99. package/dist/lib/auth/tokens.d.ts +8 -3
  100. package/dist/lib/auth/tokens.js +8 -7
  101. package/dist/lib/auth/types.d.ts +5 -1
  102. package/dist/lib/config.d.ts +18 -0
  103. package/dist/lib/config.js +10 -0
  104. package/oclif.manifest.json +2182 -744
  105. package/package.json +7 -4
@@ -1,5 +1,5 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
@@ -8,18 +8,18 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "download-all", "download", "get", "references", "update", "discard", "move", "retention", "hold", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
13
- 'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- '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, bodyArrayFields: [] },
15
- 'get': { method: 'GET', path: '/v1/files/{id}', summary: 'Get a file by id (permission-scoped)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['tags', 'references'] },
18
- '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, bodyArrayFields: [] },
19
- 'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
- '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, bodyArrayFields: [] },
21
- '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, bodyArrayFields: [] },
22
- '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, bodyArrayFields: [] },
11
+ 'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ '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: [] },
14
+ '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: [] },
15
+ '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: [] },
16
+ '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: [] },
17
+ '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'] },
18
+ '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: [] },
19
+ '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: [] },
20
+ '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: [] },
21
+ '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: [] },
22
+ '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: [] },
23
23
  };
24
24
  export default class Files extends Command {
25
25
  static description = 'Manage files';
@@ -271,10 +271,11 @@ export default class Files extends Command {
271
271
  process.stderr.write('\n');
272
272
  }
273
273
  try {
274
- // Binary response: download to file
274
+ // Binary response: to -f / --file, or to stdout when it is redirected.
275
275
  if (endpoint.isBinaryResponse) {
276
- if (!flags.file) {
277
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
276
+ const toStdout = !flags.file;
277
+ if (toStdout && process.stdout.isTTY) {
278
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
278
279
  }
279
280
  const binResponse = await apiRequest(endpoint.method, apiPath, {
280
281
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -282,24 +283,55 @@ export default class Files extends Command {
282
283
  contextName: flags.context,
283
284
  binary: true,
284
285
  });
285
- const fsModule = await import('node:fs');
286
286
  const binData = binResponse.data;
287
287
  const buffer = Buffer.isBuffer(binData)
288
288
  ? binData
289
289
  : Buffer.from(binData);
290
+ if (toStdout) {
291
+ // Nothing else may reach stdout here — it is the file.
292
+ process.stdout.write(buffer);
293
+ return;
294
+ }
295
+ const fsModule = await import('node:fs');
290
296
  fsModule.writeFileSync(flags.file, buffer);
291
297
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
292
298
  return;
293
299
  }
294
- const response = await apiRequest(endpoint.method, apiPath, {
295
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
296
- data,
297
- contextName: flags.context,
298
- acceptAny: true,
299
- });
300
+ let response;
301
+ if (endpoint.hasBinaryVariant) {
302
+ // This route answers JSON or a file depending on the request body, so the
303
+ // bytes are read first and the response's own content-type decides. Asking
304
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
305
+ const raw = await apiRequest(endpoint.method, apiPath, {
306
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
307
+ data,
308
+ contextName: flags.context,
309
+ binary: true,
310
+ });
311
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
312
+ const rawData = raw.data;
313
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
314
+ if (!isTextualMediaType(rawType)) {
315
+ if (process.stdout.isTTY) {
316
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
317
+ }
318
+ // Nothing else may reach stdout here — it is the file.
319
+ process.stdout.write(buffer);
320
+ return;
321
+ }
322
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
323
+ }
324
+ else {
325
+ response = await apiRequest(endpoint.method, apiPath, {
326
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
327
+ data,
328
+ contextName: flags.context,
329
+ acceptAny: true,
330
+ });
331
+ }
300
332
  // Check content-type for non-JSON responses (XML, text, etc.)
301
333
  const contentType = (response.headers['content-type'] || '').toLowerCase();
302
- if (contentType.includes('xml') || contentType.includes('text/')) {
334
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
303
335
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
304
336
  this.log(String(response.data));
305
337
  return;
@@ -1,5 +1,5 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = false;
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["get", "get-all", "ready", "get-3", "v1"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'get': { method: 'GET', path: '/', summary: 'GET /', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'get-all': { method: 'GET', path: '/health', summary: 'GET /health', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
- 'ready': { method: 'GET', path: '/ready', summary: 'GET /ready', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'get-3': { method: 'GET', path: '/v1/status', summary: 'GET /v1/status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- 'v1': { method: 'GET', path: '/v1', summary: 'GET /v1', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'get': { method: 'GET', path: '/', summary: 'GET /', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'get-all': { method: 'GET', path: '/health', summary: 'GET /health', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'ready': { method: 'GET', path: '/ready', summary: 'GET /ready', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'get-3': { method: 'GET', path: '/v1/status', summary: 'GET /v1/status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'v1': { method: 'GET', path: '/v1', summary: 'GET /v1', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
16
  };
17
17
  export default class Health extends Command {
18
18
  static description = 'Manage health';
@@ -259,10 +259,11 @@ export default class Health extends Command {
259
259
  process.stderr.write('\n');
260
260
  }
261
261
  try {
262
- // Binary response: download to file
262
+ // Binary response: to -f / --file, or to stdout when it is redirected.
263
263
  if (endpoint.isBinaryResponse) {
264
- if (!flags.file) {
265
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
264
+ const toStdout = !flags.file;
265
+ if (toStdout && process.stdout.isTTY) {
266
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
266
267
  }
267
268
  const binResponse = await apiRequest(endpoint.method, apiPath, {
268
269
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -270,24 +271,55 @@ export default class Health extends Command {
270
271
  contextName: flags.context,
271
272
  binary: true,
272
273
  });
273
- const fsModule = await import('node:fs');
274
274
  const binData = binResponse.data;
275
275
  const buffer = Buffer.isBuffer(binData)
276
276
  ? binData
277
277
  : Buffer.from(binData);
278
+ if (toStdout) {
279
+ // Nothing else may reach stdout here — it is the file.
280
+ process.stdout.write(buffer);
281
+ return;
282
+ }
283
+ const fsModule = await import('node:fs');
278
284
  fsModule.writeFileSync(flags.file, buffer);
279
285
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
280
286
  return;
281
287
  }
282
- const response = await apiRequest(endpoint.method, apiPath, {
283
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
284
- data,
285
- contextName: flags.context,
286
- acceptAny: true,
287
- });
288
+ let response;
289
+ if (endpoint.hasBinaryVariant) {
290
+ // This route answers JSON or a file depending on the request body, so the
291
+ // bytes are read first and the response's own content-type decides. Asking
292
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
293
+ const raw = await apiRequest(endpoint.method, apiPath, {
294
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
295
+ data,
296
+ contextName: flags.context,
297
+ binary: true,
298
+ });
299
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
300
+ const rawData = raw.data;
301
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
302
+ if (!isTextualMediaType(rawType)) {
303
+ if (process.stdout.isTTY) {
304
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
305
+ }
306
+ // Nothing else may reach stdout here — it is the file.
307
+ process.stdout.write(buffer);
308
+ return;
309
+ }
310
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
311
+ }
312
+ else {
313
+ response = await apiRequest(endpoint.method, apiPath, {
314
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
315
+ data,
316
+ contextName: flags.context,
317
+ acceptAny: true,
318
+ });
319
+ }
288
320
  // Check content-type for non-JSON responses (XML, text, etc.)
289
321
  const contentType = (response.headers['content-type'] || '').toLowerCase();
290
- if (contentType.includes('xml') || contentType.includes('text/')) {
322
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
291
323
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
292
324
  this.log(String(response.data));
293
325
  return;
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class TagConfigurations extends Command {
2
+ export default class Imports extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -1,29 +1,25 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
- const HAS_IMPORT = true;
5
+ const HAS_IMPORT = false;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "journal-coverage"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/tag-configurations', summary: 'Get all tag configurations with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/tag-configurations', summary: 'Create a new tag configuration', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['resourceTypes', 'accountTypes', 'tagIds', 'tagCodes'] },
13
- 'get': { method: 'GET', path: '/v1/tag-configurations/{id}', summary: 'Get a tag configuration by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'update': { method: 'PATCH', path: '/v1/tag-configurations/{id}', summary: 'Update a tag configuration', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['resourceTypes', 'accountTypes', 'tagIds', 'tagCodes'] },
15
- 'delete': { method: 'DELETE', path: '/v1/tag-configurations/{id}', summary: 'Delete a tag configuration', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'journal-coverage': { method: 'GET', path: '/v1/tag-configurations/{id}/journal-coverage', summary: 'How much of the journal-line amount in scope carries one of this configuration\'s tags', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'list': { method: 'GET', path: '/v1/imports', summary: 'List the tenant\'s import runs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/imports', summary: 'Import records of one resource type', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'get': { method: 'GET', path: '/v1/imports/{id}', summary: 'Read one import run back', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'delete': { method: 'DELETE', path: '/v1/imports/{id}', summary: 'Throw one import run away', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
15
  };
18
- export default class TagConfigurations extends Command {
19
- static description = 'Manage tag configurations';
16
+ export default class Imports extends Command {
17
+ static description = 'Manage imports';
20
18
  static examples = [
21
- '<%= config.bin %> tag-configurations list',
22
- '<%= config.bin %> tag-configurations get <id>',
23
- '<%= config.bin %> tag-configurations create --data \'{...}\'',
24
- '<%= config.bin %> tag-configurations update <id> --set field=value',
25
- '<%= config.bin %> tag-configurations import --file data.json',
26
- '<%= config.bin %> tag-configurations export --csv > tag-configurations.csv',
19
+ '<%= config.bin %> imports list',
20
+ '<%= config.bin %> imports get <id>',
21
+ '<%= config.bin %> imports create --data \'{...}\'',
22
+ '<%= config.bin %> imports export --csv > imports.csv',
27
23
  ];
28
24
  static args = {
29
25
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -49,7 +45,7 @@ export default class TagConfigurations extends Command {
49
45
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
50
46
  };
51
47
  async run() {
52
- const { args, flags } = await this.parse(TagConfigurations);
48
+ const { args, flags } = await this.parse(Imports);
53
49
  // Handle import action specially
54
50
  if (args.action === 'import') {
55
51
  if (!HAS_IMPORT)
@@ -90,7 +86,7 @@ export default class TagConfigurations extends Command {
90
86
  // First path param always comes from positional argument
91
87
  value = args.id;
92
88
  if (!value)
93
- this.error('Missing required argument: id. Usage: aderis tag-configurations <action> <id>');
89
+ this.error('Missing required argument: id. Usage: aderis imports <action> <id>');
94
90
  }
95
91
  else {
96
92
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -265,10 +261,11 @@ export default class TagConfigurations extends Command {
265
261
  process.stderr.write('\n');
266
262
  }
267
263
  try {
268
- // Binary response: download to file
264
+ // Binary response: to -f / --file, or to stdout when it is redirected.
269
265
  if (endpoint.isBinaryResponse) {
270
- if (!flags.file) {
271
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
266
+ const toStdout = !flags.file;
267
+ if (toStdout && process.stdout.isTTY) {
268
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
272
269
  }
273
270
  const binResponse = await apiRequest(endpoint.method, apiPath, {
274
271
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -276,24 +273,55 @@ export default class TagConfigurations extends Command {
276
273
  contextName: flags.context,
277
274
  binary: true,
278
275
  });
279
- const fsModule = await import('node:fs');
280
276
  const binData = binResponse.data;
281
277
  const buffer = Buffer.isBuffer(binData)
282
278
  ? binData
283
279
  : Buffer.from(binData);
280
+ if (toStdout) {
281
+ // Nothing else may reach stdout here — it is the file.
282
+ process.stdout.write(buffer);
283
+ return;
284
+ }
285
+ const fsModule = await import('node:fs');
284
286
  fsModule.writeFileSync(flags.file, buffer);
285
287
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
286
288
  return;
287
289
  }
288
- const response = await apiRequest(endpoint.method, apiPath, {
289
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
290
- data,
291
- contextName: flags.context,
292
- acceptAny: true,
293
- });
290
+ let response;
291
+ if (endpoint.hasBinaryVariant) {
292
+ // This route answers JSON or a file depending on the request body, so the
293
+ // bytes are read first and the response's own content-type decides. Asking
294
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
295
+ const raw = await apiRequest(endpoint.method, apiPath, {
296
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
297
+ data,
298
+ contextName: flags.context,
299
+ binary: true,
300
+ });
301
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
302
+ const rawData = raw.data;
303
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
304
+ if (!isTextualMediaType(rawType)) {
305
+ if (process.stdout.isTTY) {
306
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
307
+ }
308
+ // Nothing else may reach stdout here — it is the file.
309
+ process.stdout.write(buffer);
310
+ return;
311
+ }
312
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
313
+ }
314
+ else {
315
+ response = await apiRequest(endpoint.method, apiPath, {
316
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
317
+ data,
318
+ contextName: flags.context,
319
+ acceptAny: true,
320
+ });
321
+ }
294
322
  // Check content-type for non-JSON responses (XML, text, etc.)
295
323
  const contentType = (response.headers['content-type'] || '').toLowerCase();
296
- if (contentType.includes('xml') || contentType.includes('text/')) {
324
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
297
325
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
298
326
  this.log(String(response.data));
299
327
  return;
@@ -733,7 +761,7 @@ export default class TagConfigurations extends Command {
733
761
  }
734
762
  async runNestedImport(suffix, parentId, flags) {
735
763
  if (!parentId) {
736
- this.error(`Missing required argument: id. Usage: aderis tag-configurations import-${suffix} <parent-id> --file data.json`);
764
+ this.error(`Missing required argument: id. Usage: aderis imports import-${suffix} <parent-id> --file data.json`);
737
765
  }
738
766
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
739
767
  if (!context)
@@ -1,19 +1,20 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
2
+ import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = false;
6
6
  const HAS_EXPORT = false;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["create-upload", "email-webhook", "document-types", "uploads-files-content", "list-processing-logs", "processing-logs"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["create-upload", "email-webhook", "document-types", "uploads-files-content", "import", "list-processing-logs", "processing-logs"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'create-upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'email-webhook': { method: 'POST', path: '/v1/inbox/email-webhook/{secret}', summary: 'Email webhook for document ingestion (SendGrid inbound-parse)', pathParams: ['secret'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
- 'document-types': { method: 'GET', path: '/v1/inbox/document-types', summary: 'Get available document types and per-parent allowed types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
- 'uploads-files-content': { method: 'GET', path: '/v1/inbox/uploads/{processingId}/files/{fileId}/content', summary: 'Stream the original bytes of an uploaded inbox file', pathParams: ['fileId', 'processingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- 'list-processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs', summary: 'List recent inbox processing logs (newest first)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
11
+ 'create-upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'email-webhook': { method: 'POST', path: '/v1/inbox/email-webhook/{secret}', summary: 'Email webhook for document ingestion (SendGrid inbound-parse)', pathParams: ['secret'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'document-types': { method: 'GET', path: '/v1/inbox/document-types', summary: 'Get available document types and per-parent allowed types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
+ 'uploads-files-content': { method: 'GET', path: '/v1/inbox/uploads/{processingId}/files/{fileId}/content', summary: 'Stream the original bytes of an uploaded inbox file', pathParams: ['fileId', 'processingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'import': { method: 'POST', path: '/v1/inbox/import', summary: 'Import a provider-identified document into the inbox, refusing duplicates', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
16
+ 'list-processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs', summary: 'List recent inbox processing logs (newest first)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
+ 'processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
18
  };
18
19
  export default class Inbox extends Command {
19
20
  static description = 'Manage inbox';
@@ -259,10 +260,11 @@ export default class Inbox extends Command {
259
260
  process.stderr.write('\n');
260
261
  }
261
262
  try {
262
- // Binary response: download to file
263
+ // Binary response: to -f / --file, or to stdout when it is redirected.
263
264
  if (endpoint.isBinaryResponse) {
264
- if (!flags.file) {
265
- this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
265
+ const toStdout = !flags.file;
266
+ if (toStdout && process.stdout.isTTY) {
267
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
266
268
  }
267
269
  const binResponse = await apiRequest(endpoint.method, apiPath, {
268
270
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
@@ -270,24 +272,55 @@ export default class Inbox extends Command {
270
272
  contextName: flags.context,
271
273
  binary: true,
272
274
  });
273
- const fsModule = await import('node:fs');
274
275
  const binData = binResponse.data;
275
276
  const buffer = Buffer.isBuffer(binData)
276
277
  ? binData
277
278
  : Buffer.from(binData);
279
+ if (toStdout) {
280
+ // Nothing else may reach stdout here — it is the file.
281
+ process.stdout.write(buffer);
282
+ return;
283
+ }
284
+ const fsModule = await import('node:fs');
278
285
  fsModule.writeFileSync(flags.file, buffer);
279
286
  this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
280
287
  return;
281
288
  }
282
- const response = await apiRequest(endpoint.method, apiPath, {
283
- params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
284
- data,
285
- contextName: flags.context,
286
- acceptAny: true,
287
- });
289
+ let response;
290
+ if (endpoint.hasBinaryVariant) {
291
+ // This route answers JSON or a file depending on the request body, so the
292
+ // bytes are read first and the response's own content-type decides. Asking
293
+ // for text here would corrupt an xlsx/pdf before anything could inspect it.
294
+ const raw = await apiRequest(endpoint.method, apiPath, {
295
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
296
+ data,
297
+ contextName: flags.context,
298
+ binary: true,
299
+ });
300
+ const rawType = String(raw.headers['content-type'] || '').toLowerCase();
301
+ const rawData = raw.data;
302
+ const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
303
+ if (!isTextualMediaType(rawType)) {
304
+ if (process.stdout.isTTY) {
305
+ this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
306
+ }
307
+ // Nothing else may reach stdout here — it is the file.
308
+ process.stdout.write(buffer);
309
+ return;
310
+ }
311
+ response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
312
+ }
313
+ else {
314
+ response = await apiRequest(endpoint.method, apiPath, {
315
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
316
+ data,
317
+ contextName: flags.context,
318
+ acceptAny: true,
319
+ });
320
+ }
288
321
  // Check content-type for non-JSON responses (XML, text, etc.)
289
322
  const contentType = (response.headers['content-type'] || '').toLowerCase();
290
- if (contentType.includes('xml') || contentType.includes('text/')) {
323
+ if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
291
324
  // Output raw content for XML/text responses (CSV, plain, HTML, etc.)
292
325
  this.log(String(response.data));
293
326
  return;
@@ -0,0 +1,40 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class IntegrationIdentities extends Command {
3
+ static description: string;
4
+ static examples: never[];
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
+ 'secret-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ };
29
+ run(): Promise<void>;
30
+ private runImport;
31
+ private runExport;
32
+ private runCamt053Import;
33
+ private runNestedImport;
34
+ private parseFilterValue;
35
+ private sanitizeOutput;
36
+ private getColumns;
37
+ private getValue;
38
+ private printTable;
39
+ private printCsv;
40
+ }