@aderis-erp/cli 1.0.14 → 1.0.17

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 (167) hide show
  1. package/README.md +30 -8
  2. package/dist/commands/access-groups.d.ts +0 -1
  3. package/dist/commands/access-groups.js +22 -78
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +22 -78
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +28 -82
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +24 -80
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +24 -79
  12. package/dist/commands/analytics.d.ts +39 -0
  13. package/dist/commands/analytics.js +1113 -0
  14. package/dist/commands/{documents.d.ts → api-keys.d.ts} +1 -2
  15. package/dist/commands/api-keys.js +1117 -0
  16. package/dist/commands/audit-log.d.ts +0 -1
  17. package/dist/commands/audit-log.js +22 -78
  18. package/dist/commands/auth/login.d.ts +9 -0
  19. package/dist/commands/auth/login.js +83 -21
  20. package/dist/commands/auth/logout.d.ts +6 -0
  21. package/dist/commands/auth/logout.js +23 -6
  22. package/dist/commands/auth/status.js +46 -23
  23. package/dist/commands/auth/tenant.js +15 -9
  24. package/dist/commands/auth.d.ts +0 -1
  25. package/dist/commands/auth.js +37 -51
  26. package/dist/commands/bank-accounts.d.ts +0 -1
  27. package/dist/commands/bank-accounts.js +22 -78
  28. package/dist/commands/bank-integrations.d.ts +0 -1
  29. package/dist/commands/bank-integrations.js +22 -78
  30. package/dist/commands/business-partners.d.ts +1 -1
  31. package/dist/commands/business-partners.js +38 -84
  32. package/dist/commands/chat.d.ts +0 -1
  33. package/dist/commands/chat.js +22 -78
  34. package/dist/commands/config/add.d.ts +12 -0
  35. package/dist/commands/config/add.js +97 -17
  36. package/dist/commands/config/current.js +12 -4
  37. package/dist/commands/config/list.js +12 -4
  38. package/dist/commands/config/set-api-key.d.ts +10 -0
  39. package/dist/commands/config/set-api-key.js +49 -0
  40. package/dist/commands/config/use.js +3 -4
  41. package/dist/commands/credit-card-accounts.d.ts +0 -1
  42. package/dist/commands/credit-card-accounts.js +22 -78
  43. package/dist/commands/delivery-notes.d.ts +0 -1
  44. package/dist/commands/delivery-notes.js +29 -84
  45. package/dist/commands/dunning-configs.d.ts +0 -1
  46. package/dist/commands/dunning-configs.js +22 -78
  47. package/dist/commands/dunning-reminders.d.ts +0 -1
  48. package/dist/commands/dunning-reminders.js +22 -78
  49. package/dist/commands/dunning-runs.d.ts +0 -1
  50. package/dist/commands/dunning-runs.js +22 -78
  51. package/dist/commands/email-transactions.d.ts +0 -1
  52. package/dist/commands/email-transactions.js +22 -78
  53. package/dist/commands/employees.d.ts +0 -1
  54. package/dist/commands/employees.js +24 -80
  55. package/dist/commands/entity-configurations.d.ts +0 -1
  56. package/dist/commands/entity-configurations.js +22 -78
  57. package/dist/commands/event-processor-mappings.d.ts +0 -1
  58. package/dist/commands/event-processor-mappings.js +22 -78
  59. package/dist/commands/{files-internal.d.ts → files.d.ts} +1 -2
  60. package/dist/commands/{files-internal.js → files.js} +40 -90
  61. package/dist/commands/health.d.ts +0 -1
  62. package/dist/commands/health.js +22 -78
  63. package/dist/commands/inbox.d.ts +0 -1
  64. package/dist/commands/inbox.js +22 -78
  65. package/dist/commands/integrations.d.ts +0 -1
  66. package/dist/commands/integrations.js +22 -78
  67. package/dist/commands/item-categories.d.ts +0 -1
  68. package/dist/commands/item-categories.js +22 -78
  69. package/dist/commands/items.d.ts +0 -1
  70. package/dist/commands/items.js +24 -80
  71. package/dist/commands/journal-lines.d.ts +0 -1
  72. package/dist/commands/journal-lines.js +22 -78
  73. package/dist/commands/journals.d.ts +1 -1
  74. package/dist/commands/journals.js +29 -81
  75. package/dist/commands/ledgers.d.ts +0 -1
  76. package/dist/commands/ledgers.js +26 -79
  77. package/dist/commands/nango-integrations.d.ts +39 -0
  78. package/dist/commands/nango-integrations.js +1115 -0
  79. package/dist/commands/office-locations.d.ts +0 -1
  80. package/dist/commands/office-locations.js +22 -78
  81. package/dist/commands/payment-methods.d.ts +0 -1
  82. package/dist/commands/payment-methods.js +22 -78
  83. package/dist/commands/payment-runs.d.ts +0 -1
  84. package/dist/commands/payment-runs.js +24 -79
  85. package/dist/commands/payment-terms.d.ts +0 -1
  86. package/dist/commands/payment-terms.js +22 -78
  87. package/dist/commands/payments.d.ts +0 -1
  88. package/dist/commands/payments.js +22 -78
  89. package/dist/commands/price-lists.d.ts +0 -1
  90. package/dist/commands/price-lists.js +22 -78
  91. package/dist/commands/processor-mappings.d.ts +0 -1
  92. package/dist/commands/processor-mappings.js +22 -78
  93. package/dist/commands/processors.d.ts +0 -1
  94. package/dist/commands/processors.js +22 -78
  95. package/dist/commands/purchase-order-types.d.ts +0 -1
  96. package/dist/commands/purchase-order-types.js +22 -78
  97. package/dist/commands/purchase-orders.d.ts +1 -1
  98. package/dist/commands/purchase-orders.js +35 -84
  99. package/dist/commands/reports.d.ts +0 -1
  100. package/dist/commands/reports.js +23 -79
  101. package/dist/commands/salary-types.d.ts +0 -1
  102. package/dist/commands/salary-types.js +22 -78
  103. package/dist/commands/sales-invoices.d.ts +0 -1
  104. package/dist/commands/sales-invoices.js +38 -89
  105. package/dist/commands/sales-orders.d.ts +0 -1
  106. package/dist/commands/sales-orders.js +26 -82
  107. package/dist/commands/saved-views.d.ts +0 -1
  108. package/dist/commands/saved-views.js +22 -78
  109. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  110. package/dist/commands/scheduled-processor-mappings.js +22 -78
  111. package/dist/commands/schedules.d.ts +0 -1
  112. package/dist/commands/schedules.js +22 -78
  113. package/dist/commands/switch.js +14 -7
  114. package/dist/commands/tag-configurations.d.ts +39 -0
  115. package/dist/commands/{documents.js → tag-configurations.js} +40 -96
  116. package/dist/commands/tags.d.ts +0 -1
  117. package/dist/commands/tags.js +22 -78
  118. package/dist/commands/tax-codes.d.ts +0 -1
  119. package/dist/commands/tax-codes.js +22 -78
  120. package/dist/commands/tax-matrix.d.ts +0 -1
  121. package/dist/commands/tax-matrix.js +22 -78
  122. package/dist/commands/tenants.d.ts +0 -1
  123. package/dist/commands/tenants.js +24 -79
  124. package/dist/commands/text-templates.d.ts +0 -1
  125. package/dist/commands/text-templates.js +22 -78
  126. package/dist/commands/tools.d.ts +0 -1
  127. package/dist/commands/tools.js +22 -78
  128. package/dist/commands/vat-declarations.d.ts +1 -1
  129. package/dist/commands/vat-declarations.js +28 -79
  130. package/dist/commands/vendor-invoices.d.ts +0 -1
  131. package/dist/commands/vendor-invoices.js +31 -86
  132. package/dist/commands/webhooks.d.ts +0 -1
  133. package/dist/commands/webhooks.js +22 -78
  134. package/dist/commands/workflows.d.ts +0 -1
  135. package/dist/commands/workflows.js +22 -78
  136. package/dist/lib/api-client.js +8 -6
  137. package/dist/lib/auth/constants.d.ts +11 -0
  138. package/dist/lib/auth/constants.js +11 -0
  139. package/dist/lib/auth/errors.d.ts +17 -0
  140. package/dist/lib/auth/errors.js +22 -0
  141. package/dist/lib/auth/flow.d.ts +27 -0
  142. package/dist/lib/auth/flow.js +163 -0
  143. package/dist/lib/auth/guards.d.ts +5 -0
  144. package/dist/lib/auth/guards.js +14 -0
  145. package/dist/lib/auth/http.d.ts +16 -0
  146. package/dist/lib/auth/http.js +49 -0
  147. package/dist/lib/auth/index.d.ts +49 -0
  148. package/dist/lib/auth/index.js +217 -0
  149. package/dist/lib/auth/keycloak-provider.d.ts +75 -0
  150. package/dist/lib/auth/keycloak-provider.js +215 -0
  151. package/dist/lib/auth/pages.d.ts +25 -0
  152. package/dist/lib/auth/pages.js +123 -0
  153. package/dist/lib/auth/pkce.d.ts +11 -0
  154. package/dist/lib/auth/pkce.js +17 -0
  155. package/dist/lib/auth/tokens.d.ts +54 -0
  156. package/dist/lib/auth/tokens.js +104 -0
  157. package/dist/lib/auth/types.d.ts +57 -0
  158. package/dist/lib/auth/types.js +1 -0
  159. package/dist/lib/auth/validated-url.d.ts +6 -0
  160. package/dist/lib/auth/validated-url.js +53 -0
  161. package/dist/lib/base-api-command.js +6 -4
  162. package/dist/lib/config.d.ts +44 -4
  163. package/dist/lib/config.js +55 -19
  164. package/oclif.manifest.json +1181 -330
  165. package/package.json +3 -2
  166. package/dist/lib/auth.d.ts +0 -35
  167. package/dist/lib/auth.js +0 -566
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
6
6
  const HAS_EXPORT = true;
@@ -28,7 +28,7 @@ export default class EventProcessorMappings extends Command {
28
28
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
29
29
  };
30
30
  static flags = {
31
- context: Flags.string({ char: 'c', description: 'Context to use' }),
31
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
32
32
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
33
33
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
34
34
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -42,7 +42,7 @@ export default class EventProcessorMappings extends Command {
42
42
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
43
43
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
44
44
  query: Flags.string({ description: 'Additional query params as JSON' }),
45
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
45
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
46
46
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
47
47
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
48
48
  };
@@ -100,9 +100,9 @@ export default class EventProcessorMappings extends Command {
100
100
  }
101
101
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
102
102
  if (!context)
103
- this.error('No context configured. Run: aderis auth login --new');
104
- if (!context.tokens)
105
- this.error('Not logged in. Run: aderis auth login');
103
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
104
+ if (!hasCredentials(context))
105
+ this.error(missingCredentialsMessage(context));
106
106
  const queryParams = {};
107
107
  if (flags.page)
108
108
  queryParams.page = flags.page;
@@ -114,6 +114,8 @@ export default class EventProcessorMappings extends Command {
114
114
  queryParams.search = flags.search;
115
115
  if (flags['sort-by'])
116
116
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
117
+ if (flags.fields)
118
+ queryParams.fields = flags.fields;
117
119
  if (flags.query) {
118
120
  try {
119
121
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -316,11 +318,6 @@ export default class EventProcessorMappings extends Command {
316
318
  output = obj[arrayKeys[0]];
317
319
  }
318
320
  }
319
- // Filter fields if --fields flag is provided
320
- if (flags.fields) {
321
- const fieldList = flags.fields.split(',').map(f => f.trim());
322
- output = this.filterFields(output, fieldList);
323
- }
324
321
  // Sanitize output if requested
325
322
  if (flags.sanitize) {
326
323
  output = this.sanitizeOutput(output);
@@ -345,9 +342,9 @@ export default class EventProcessorMappings extends Command {
345
342
  async runImport(flags) {
346
343
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
347
344
  if (!context)
348
- this.error('No context configured. Run: aderis auth login --new');
349
- if (!context.tokens)
350
- this.error('Not logged in. Run: aderis auth login');
345
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
346
+ if (!hasCredentials(context))
347
+ this.error(missingCredentialsMessage(context));
351
348
  // Get data from file, --data, or --set flags
352
349
  let items = [];
353
350
  if (flags.file) {
@@ -557,9 +554,9 @@ export default class EventProcessorMappings extends Command {
557
554
  async runExport(flags) {
558
555
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
559
556
  if (!context)
560
- this.error('No context configured. Run: aderis auth login --new');
561
- if (!context.tokens)
562
- this.error('Not logged in. Run: aderis auth login');
557
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
558
+ if (!hasCredentials(context))
559
+ this.error(missingCredentialsMessage(context));
563
560
  const listEndpoint = ENDPOINTS['list'];
564
561
  if (!listEndpoint)
565
562
  this.error('Export requires a list endpoint');
@@ -575,6 +572,8 @@ export default class EventProcessorMappings extends Command {
575
572
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
576
573
  if (flags.include)
577
574
  baseParams.include = flags.include;
575
+ if (flags.fields)
576
+ baseParams.fields = flags.fields;
578
577
  if (flags.query) {
579
578
  try {
580
579
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -629,12 +628,7 @@ export default class EventProcessorMappings extends Command {
629
628
  }
630
629
  }
631
630
  process.stderr.write(' done\n');
632
- // Filter fields if specified
633
631
  let output = allItems;
634
- if (flags.fields) {
635
- const fieldList = flags.fields.split(',').map(f => f.trim());
636
- output = this.filterFields(allItems, fieldList);
637
- }
638
632
  // Sanitize output if requested
639
633
  if (flags.sanitize) {
640
634
  output = this.sanitizeOutput(output);
@@ -651,9 +645,9 @@ export default class EventProcessorMappings extends Command {
651
645
  async runCamt053Import(flags) {
652
646
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
653
647
  if (!context)
654
- this.error('No context configured. Run: aderis auth login --new');
655
- if (!context.tokens)
656
- this.error('Not logged in. Run: aderis auth login');
648
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
649
+ if (!hasCredentials(context))
650
+ this.error(missingCredentialsMessage(context));
657
651
  if (!flags.file) {
658
652
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
659
653
  }
@@ -741,9 +735,9 @@ export default class EventProcessorMappings extends Command {
741
735
  }
742
736
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
743
737
  if (!context)
744
- this.error('No context configured. Run: aderis auth login --new');
745
- if (!context.tokens)
746
- this.error('Not logged in. Run: aderis auth login');
738
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
739
+ if (!hasCredentials(context))
740
+ this.error(missingCredentialsMessage(context));
747
741
  // Get data from file, --data, or --set flags
748
742
  let items = [];
749
743
  if (flags.file) {
@@ -963,56 +957,6 @@ export default class EventProcessorMappings extends Command {
963
957
  return `\$${op}`;
964
958
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
965
959
  }
966
- filterFields(data, fields) {
967
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
968
- const pick = (obj, keys) => {
969
- // Group keys by their first segment so sibling sub-paths
970
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
971
- // and don't clobber each other on merge.
972
- const directKeys = new Set();
973
- const nestedKeys = new Map();
974
- for (const key of keys) {
975
- if (key.split('.').some(isUnsafeKey))
976
- continue;
977
- if (key.includes('.')) {
978
- const [first, ...rest] = key.split('.');
979
- if (isUnsafeKey(first))
980
- continue;
981
- const list = nestedKeys.get(first);
982
- if (list) {
983
- list.push(rest.join('.'));
984
- }
985
- else {
986
- nestedKeys.set(first, [rest.join('.')]);
987
- }
988
- }
989
- else {
990
- directKeys.add(key);
991
- }
992
- }
993
- const result = {};
994
- for (const key of directKeys) {
995
- if (key in obj)
996
- result[key] = obj[key];
997
- }
998
- for (const [first, restKeys] of nestedKeys) {
999
- const val = obj[first];
1000
- if (!val || typeof val !== 'object')
1001
- continue;
1002
- if (Array.isArray(val)) {
1003
- result[first] = val.map(item => pick(item, restKeys));
1004
- }
1005
- else {
1006
- result[first] = pick(val, restKeys);
1007
- }
1008
- }
1009
- return result;
1010
- };
1011
- if (Array.isArray(data)) {
1012
- return data.map(item => pick(item, fields));
1013
- }
1014
- return pick(data, fields);
1015
- }
1016
960
  sanitizeOutput(data) {
1017
961
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1018
962
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class FilesInternal extends Command {
2
+ export default class Files extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -31,7 +31,6 @@ export default class FilesInternal extends Command {
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
33
  private parseFilterValue;
34
- private filterFields;
35
34
  private sanitizeOutput;
36
35
  private getColumns;
37
36
  private getValue;
@@ -1,36 +1,42 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
6
- const HAS_EXPORT = false;
6
+ const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["create", "download-all", "download", "references", "update", "move", "renditions", "retention", "hold"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
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: [] },
11
12
  'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
12
13
  'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
13
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: [] },
14
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: [] },
15
- 'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], 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: [] },
16
19
  'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
- 'renditions': { method: 'POST', path: '/v1/files/{fileId}/renditions', summary: 'Register a business-significant rendition', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
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: [] },
19
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: [] },
20
23
  };
21
- export default class FilesInternal extends Command {
22
- static description = 'Manage files internal';
24
+ export default class Files extends Command {
25
+ static description = 'Manage files';
23
26
  static examples = [
24
- '<%= config.bin %> files-internal create --data \'{...}\'',
25
- '<%= config.bin %> files-internal update <id> --set field=value',
26
- '<%= config.bin %> files-internal import --file data.json',
27
+ '<%= config.bin %> files list',
28
+ '<%= config.bin %> files get <id>',
29
+ '<%= config.bin %> files create --data \'{...}\'',
30
+ '<%= config.bin %> files update <id> --set field=value',
31
+ '<%= config.bin %> files import --file data.json',
32
+ '<%= config.bin %> files export --csv > files.csv',
27
33
  ];
28
34
  static args = {
29
35
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
30
36
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
31
37
  };
32
38
  static flags = {
33
- context: Flags.string({ char: 'c', description: 'Context to use' }),
39
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
34
40
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
35
41
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
36
42
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -44,12 +50,12 @@ export default class FilesInternal extends Command {
44
50
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
45
51
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
46
52
  query: Flags.string({ description: 'Additional query params as JSON' }),
47
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
53
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
48
54
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
49
55
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
50
56
  };
51
57
  async run() {
52
- const { args, flags } = await this.parse(FilesInternal);
58
+ const { args, flags } = await this.parse(Files);
53
59
  // Handle import action specially
54
60
  if (args.action === 'import') {
55
61
  if (!HAS_IMPORT)
@@ -90,7 +96,7 @@ export default class FilesInternal extends Command {
90
96
  // First path param always comes from positional argument
91
97
  value = args.id;
92
98
  if (!value)
93
- this.error('Missing required argument: id. Usage: aderis files-internal <action> <id>');
99
+ this.error('Missing required argument: id. Usage: aderis files <action> <id>');
94
100
  }
95
101
  else {
96
102
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -102,9 +108,9 @@ export default class FilesInternal extends Command {
102
108
  }
103
109
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
104
110
  if (!context)
105
- this.error('No context configured. Run: aderis auth login --new');
106
- if (!context.tokens)
107
- this.error('Not logged in. Run: aderis auth login');
111
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
112
+ if (!hasCredentials(context))
113
+ this.error(missingCredentialsMessage(context));
108
114
  const queryParams = {};
109
115
  if (flags.page)
110
116
  queryParams.page = flags.page;
@@ -116,6 +122,8 @@ export default class FilesInternal extends Command {
116
122
  queryParams.search = flags.search;
117
123
  if (flags['sort-by'])
118
124
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
125
+ if (flags.fields)
126
+ queryParams.fields = flags.fields;
119
127
  if (flags.query) {
120
128
  try {
121
129
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -318,11 +326,6 @@ export default class FilesInternal extends Command {
318
326
  output = obj[arrayKeys[0]];
319
327
  }
320
328
  }
321
- // Filter fields if --fields flag is provided
322
- if (flags.fields) {
323
- const fieldList = flags.fields.split(',').map(f => f.trim());
324
- output = this.filterFields(output, fieldList);
325
- }
326
329
  // Sanitize output if requested
327
330
  if (flags.sanitize) {
328
331
  output = this.sanitizeOutput(output);
@@ -347,9 +350,9 @@ export default class FilesInternal extends Command {
347
350
  async runImport(flags) {
348
351
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
349
352
  if (!context)
350
- this.error('No context configured. Run: aderis auth login --new');
351
- if (!context.tokens)
352
- this.error('Not logged in. Run: aderis auth login');
353
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
354
+ if (!hasCredentials(context))
355
+ this.error(missingCredentialsMessage(context));
353
356
  // Get data from file, --data, or --set flags
354
357
  let items = [];
355
358
  if (flags.file) {
@@ -559,9 +562,9 @@ export default class FilesInternal extends Command {
559
562
  async runExport(flags) {
560
563
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
561
564
  if (!context)
562
- this.error('No context configured. Run: aderis auth login --new');
563
- if (!context.tokens)
564
- this.error('Not logged in. Run: aderis auth login');
565
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
566
+ if (!hasCredentials(context))
567
+ this.error(missingCredentialsMessage(context));
565
568
  const listEndpoint = ENDPOINTS['list'];
566
569
  if (!listEndpoint)
567
570
  this.error('Export requires a list endpoint');
@@ -577,6 +580,8 @@ export default class FilesInternal extends Command {
577
580
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
578
581
  if (flags.include)
579
582
  baseParams.include = flags.include;
583
+ if (flags.fields)
584
+ baseParams.fields = flags.fields;
580
585
  if (flags.query) {
581
586
  try {
582
587
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -631,12 +636,7 @@ export default class FilesInternal extends Command {
631
636
  }
632
637
  }
633
638
  process.stderr.write(' done\n');
634
- // Filter fields if specified
635
639
  let output = allItems;
636
- if (flags.fields) {
637
- const fieldList = flags.fields.split(',').map(f => f.trim());
638
- output = this.filterFields(allItems, fieldList);
639
- }
640
640
  // Sanitize output if requested
641
641
  if (flags.sanitize) {
642
642
  output = this.sanitizeOutput(output);
@@ -653,9 +653,9 @@ export default class FilesInternal extends Command {
653
653
  async runCamt053Import(flags) {
654
654
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
655
655
  if (!context)
656
- this.error('No context configured. Run: aderis auth login --new');
657
- if (!context.tokens)
658
- this.error('Not logged in. Run: aderis auth login');
656
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
657
+ if (!hasCredentials(context))
658
+ this.error(missingCredentialsMessage(context));
659
659
  if (!flags.file) {
660
660
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
661
661
  }
@@ -739,13 +739,13 @@ export default class FilesInternal extends Command {
739
739
  }
740
740
  async runNestedImport(suffix, parentId, flags) {
741
741
  if (!parentId) {
742
- this.error(`Missing required argument: id. Usage: aderis files-internal import-${suffix} <parent-id> --file data.json`);
742
+ this.error(`Missing required argument: id. Usage: aderis files import-${suffix} <parent-id> --file data.json`);
743
743
  }
744
744
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
745
745
  if (!context)
746
- this.error('No context configured. Run: aderis auth login --new');
747
- if (!context.tokens)
748
- this.error('Not logged in. Run: aderis auth login');
746
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
747
+ if (!hasCredentials(context))
748
+ this.error(missingCredentialsMessage(context));
749
749
  // Get data from file, --data, or --set flags
750
750
  let items = [];
751
751
  if (flags.file) {
@@ -965,56 +965,6 @@ export default class FilesInternal extends Command {
965
965
  return `\$${op}`;
966
966
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
967
967
  }
968
- filterFields(data, fields) {
969
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
970
- const pick = (obj, keys) => {
971
- // Group keys by their first segment so sibling sub-paths
972
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
973
- // and don't clobber each other on merge.
974
- const directKeys = new Set();
975
- const nestedKeys = new Map();
976
- for (const key of keys) {
977
- if (key.split('.').some(isUnsafeKey))
978
- continue;
979
- if (key.includes('.')) {
980
- const [first, ...rest] = key.split('.');
981
- if (isUnsafeKey(first))
982
- continue;
983
- const list = nestedKeys.get(first);
984
- if (list) {
985
- list.push(rest.join('.'));
986
- }
987
- else {
988
- nestedKeys.set(first, [rest.join('.')]);
989
- }
990
- }
991
- else {
992
- directKeys.add(key);
993
- }
994
- }
995
- const result = {};
996
- for (const key of directKeys) {
997
- if (key in obj)
998
- result[key] = obj[key];
999
- }
1000
- for (const [first, restKeys] of nestedKeys) {
1001
- const val = obj[first];
1002
- if (!val || typeof val !== 'object')
1003
- continue;
1004
- if (Array.isArray(val)) {
1005
- result[first] = val.map(item => pick(item, restKeys));
1006
- }
1007
- else {
1008
- result[first] = pick(val, restKeys);
1009
- }
1010
- }
1011
- return result;
1012
- };
1013
- if (Array.isArray(data)) {
1014
- return data.map(item => pick(item, fields));
1015
- }
1016
- return pick(data, fields);
1017
- }
1018
968
  sanitizeOutput(data) {
1019
969
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1020
970
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -31,7 +31,6 @@ export default class Health extends Command {
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
33
  private parseFilterValue;
34
- private filterFields;
35
34
  private sanitizeOutput;
36
35
  private getColumns;
37
36
  private getValue;
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
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;
@@ -24,7 +24,7 @@ export default class Health extends Command {
24
24
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
25
25
  };
26
26
  static flags = {
27
- context: Flags.string({ char: 'c', description: 'Context to use' }),
27
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
28
28
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
29
29
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
30
30
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -38,7 +38,7 @@ export default class Health extends Command {
38
38
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
39
39
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
40
40
  query: Flags.string({ description: 'Additional query params as JSON' }),
41
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
41
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
42
42
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
43
43
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
44
44
  };
@@ -96,9 +96,9 @@ export default class Health extends Command {
96
96
  }
97
97
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
98
98
  if (!context)
99
- this.error('No context configured. Run: aderis auth login --new');
100
- if (!context.tokens)
101
- this.error('Not logged in. Run: aderis auth login');
99
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
100
+ if (!hasCredentials(context))
101
+ this.error(missingCredentialsMessage(context));
102
102
  const queryParams = {};
103
103
  if (flags.page)
104
104
  queryParams.page = flags.page;
@@ -110,6 +110,8 @@ export default class Health extends Command {
110
110
  queryParams.search = flags.search;
111
111
  if (flags['sort-by'])
112
112
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
113
+ if (flags.fields)
114
+ queryParams.fields = flags.fields;
113
115
  if (flags.query) {
114
116
  try {
115
117
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -312,11 +314,6 @@ export default class Health extends Command {
312
314
  output = obj[arrayKeys[0]];
313
315
  }
314
316
  }
315
- // Filter fields if --fields flag is provided
316
- if (flags.fields) {
317
- const fieldList = flags.fields.split(',').map(f => f.trim());
318
- output = this.filterFields(output, fieldList);
319
- }
320
317
  // Sanitize output if requested
321
318
  if (flags.sanitize) {
322
319
  output = this.sanitizeOutput(output);
@@ -341,9 +338,9 @@ export default class Health extends Command {
341
338
  async runImport(flags) {
342
339
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
343
340
  if (!context)
344
- this.error('No context configured. Run: aderis auth login --new');
345
- if (!context.tokens)
346
- this.error('Not logged in. Run: aderis auth login');
341
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
342
+ if (!hasCredentials(context))
343
+ this.error(missingCredentialsMessage(context));
347
344
  // Get data from file, --data, or --set flags
348
345
  let items = [];
349
346
  if (flags.file) {
@@ -553,9 +550,9 @@ export default class Health extends Command {
553
550
  async runExport(flags) {
554
551
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
555
552
  if (!context)
556
- this.error('No context configured. Run: aderis auth login --new');
557
- if (!context.tokens)
558
- this.error('Not logged in. Run: aderis auth login');
553
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
554
+ if (!hasCredentials(context))
555
+ this.error(missingCredentialsMessage(context));
559
556
  const listEndpoint = ENDPOINTS['list'];
560
557
  if (!listEndpoint)
561
558
  this.error('Export requires a list endpoint');
@@ -571,6 +568,8 @@ export default class Health extends Command {
571
568
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
572
569
  if (flags.include)
573
570
  baseParams.include = flags.include;
571
+ if (flags.fields)
572
+ baseParams.fields = flags.fields;
574
573
  if (flags.query) {
575
574
  try {
576
575
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -625,12 +624,7 @@ export default class Health extends Command {
625
624
  }
626
625
  }
627
626
  process.stderr.write(' done\n');
628
- // Filter fields if specified
629
627
  let output = allItems;
630
- if (flags.fields) {
631
- const fieldList = flags.fields.split(',').map(f => f.trim());
632
- output = this.filterFields(allItems, fieldList);
633
- }
634
628
  // Sanitize output if requested
635
629
  if (flags.sanitize) {
636
630
  output = this.sanitizeOutput(output);
@@ -647,9 +641,9 @@ export default class Health extends Command {
647
641
  async runCamt053Import(flags) {
648
642
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
649
643
  if (!context)
650
- this.error('No context configured. Run: aderis auth login --new');
651
- if (!context.tokens)
652
- this.error('Not logged in. Run: aderis auth login');
644
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
645
+ if (!hasCredentials(context))
646
+ this.error(missingCredentialsMessage(context));
653
647
  if (!flags.file) {
654
648
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
655
649
  }
@@ -737,9 +731,9 @@ export default class Health extends Command {
737
731
  }
738
732
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
739
733
  if (!context)
740
- this.error('No context configured. Run: aderis auth login --new');
741
- if (!context.tokens)
742
- this.error('Not logged in. Run: aderis auth login');
734
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
735
+ if (!hasCredentials(context))
736
+ this.error(missingCredentialsMessage(context));
743
737
  // Get data from file, --data, or --set flags
744
738
  let items = [];
745
739
  if (flags.file) {
@@ -959,56 +953,6 @@ export default class Health extends Command {
959
953
  return `\$${op}`;
960
954
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
961
955
  }
962
- filterFields(data, fields) {
963
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
964
- const pick = (obj, keys) => {
965
- // Group keys by their first segment so sibling sub-paths
966
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
967
- // and don't clobber each other on merge.
968
- const directKeys = new Set();
969
- const nestedKeys = new Map();
970
- for (const key of keys) {
971
- if (key.split('.').some(isUnsafeKey))
972
- continue;
973
- if (key.includes('.')) {
974
- const [first, ...rest] = key.split('.');
975
- if (isUnsafeKey(first))
976
- continue;
977
- const list = nestedKeys.get(first);
978
- if (list) {
979
- list.push(rest.join('.'));
980
- }
981
- else {
982
- nestedKeys.set(first, [rest.join('.')]);
983
- }
984
- }
985
- else {
986
- directKeys.add(key);
987
- }
988
- }
989
- const result = {};
990
- for (const key of directKeys) {
991
- if (key in obj)
992
- result[key] = obj[key];
993
- }
994
- for (const [first, restKeys] of nestedKeys) {
995
- const val = obj[first];
996
- if (!val || typeof val !== 'object')
997
- continue;
998
- if (Array.isArray(val)) {
999
- result[first] = val.map(item => pick(item, restKeys));
1000
- }
1001
- else {
1002
- result[first] = pick(val, restKeys);
1003
- }
1004
- }
1005
- return result;
1006
- };
1007
- if (Array.isArray(data)) {
1008
- return data.map(item => pick(item, fields));
1009
- }
1010
- return pick(data, fields);
1011
- }
1012
956
  sanitizeOutput(data) {
1013
957
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1014
958
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;