@aderis-erp/cli 1.0.15 → 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 (163) 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 +5 -61
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +5 -61
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +9 -63
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +5 -61
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +5 -61
  12. package/dist/commands/analytics.d.ts +0 -1
  13. package/dist/commands/analytics.js +5 -61
  14. package/dist/commands/api-keys.d.ts +39 -0
  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 +5 -61
  18. package/dist/commands/auth/login.d.ts +9 -0
  19. package/dist/commands/auth/login.js +73 -20
  20. package/dist/commands/auth/logout.d.ts +4 -0
  21. package/dist/commands/auth/logout.js +4 -0
  22. package/dist/commands/auth/status.js +26 -17
  23. package/dist/commands/auth/tenant.js +3 -6
  24. package/dist/commands/auth.d.ts +0 -1
  25. package/dist/commands/auth.js +5 -34
  26. package/dist/commands/bank-accounts.d.ts +0 -1
  27. package/dist/commands/bank-accounts.js +5 -61
  28. package/dist/commands/bank-integrations.d.ts +0 -1
  29. package/dist/commands/bank-integrations.js +5 -61
  30. package/dist/commands/business-partners.d.ts +1 -1
  31. package/dist/commands/business-partners.js +14 -63
  32. package/dist/commands/chat.d.ts +0 -1
  33. package/dist/commands/chat.js +5 -61
  34. package/dist/commands/config/add.d.ts +3 -0
  35. package/dist/commands/config/add.js +34 -16
  36. package/dist/commands/config/current.js +3 -0
  37. package/dist/commands/config/list.js +3 -0
  38. package/dist/commands/credit-card-accounts.d.ts +0 -1
  39. package/dist/commands/credit-card-accounts.js +5 -61
  40. package/dist/commands/delivery-notes.d.ts +0 -1
  41. package/dist/commands/delivery-notes.js +7 -63
  42. package/dist/commands/dunning-configs.d.ts +0 -1
  43. package/dist/commands/dunning-configs.js +5 -61
  44. package/dist/commands/dunning-reminders.d.ts +0 -1
  45. package/dist/commands/dunning-reminders.js +5 -61
  46. package/dist/commands/dunning-runs.d.ts +0 -1
  47. package/dist/commands/dunning-runs.js +5 -61
  48. package/dist/commands/email-transactions.d.ts +0 -1
  49. package/dist/commands/email-transactions.js +5 -61
  50. package/dist/commands/employees.d.ts +0 -1
  51. package/dist/commands/employees.js +5 -61
  52. package/dist/commands/entity-configurations.d.ts +0 -1
  53. package/dist/commands/entity-configurations.js +5 -61
  54. package/dist/commands/event-processor-mappings.d.ts +0 -1
  55. package/dist/commands/event-processor-mappings.js +5 -61
  56. package/dist/commands/files.d.ts +0 -1
  57. package/dist/commands/files.js +5 -61
  58. package/dist/commands/health.d.ts +0 -1
  59. package/dist/commands/health.js +5 -61
  60. package/dist/commands/inbox.d.ts +0 -1
  61. package/dist/commands/inbox.js +5 -61
  62. package/dist/commands/integrations.d.ts +0 -1
  63. package/dist/commands/integrations.js +5 -61
  64. package/dist/commands/item-categories.d.ts +0 -1
  65. package/dist/commands/item-categories.js +5 -61
  66. package/dist/commands/items.d.ts +0 -1
  67. package/dist/commands/items.js +5 -61
  68. package/dist/commands/journal-lines.d.ts +0 -1
  69. package/dist/commands/journal-lines.js +5 -61
  70. package/dist/commands/journals.d.ts +1 -1
  71. package/dist/commands/journals.js +9 -62
  72. package/dist/commands/ledgers.d.ts +0 -1
  73. package/dist/commands/ledgers.js +5 -61
  74. package/dist/commands/nango-integrations.d.ts +39 -0
  75. package/dist/commands/nango-integrations.js +1115 -0
  76. package/dist/commands/office-locations.d.ts +0 -1
  77. package/dist/commands/office-locations.js +5 -61
  78. package/dist/commands/payment-methods.d.ts +0 -1
  79. package/dist/commands/payment-methods.js +5 -61
  80. package/dist/commands/payment-runs.d.ts +0 -1
  81. package/dist/commands/payment-runs.js +5 -61
  82. package/dist/commands/payment-terms.d.ts +0 -1
  83. package/dist/commands/payment-terms.js +5 -61
  84. package/dist/commands/payments.d.ts +0 -1
  85. package/dist/commands/payments.js +5 -61
  86. package/dist/commands/price-lists.d.ts +0 -1
  87. package/dist/commands/price-lists.js +5 -61
  88. package/dist/commands/processor-mappings.d.ts +0 -1
  89. package/dist/commands/processor-mappings.js +5 -61
  90. package/dist/commands/processors.d.ts +0 -1
  91. package/dist/commands/processors.js +5 -61
  92. package/dist/commands/purchase-order-types.d.ts +0 -1
  93. package/dist/commands/purchase-order-types.js +5 -61
  94. package/dist/commands/purchase-orders.d.ts +0 -1
  95. package/dist/commands/purchase-orders.js +7 -63
  96. package/dist/commands/reports.d.ts +0 -1
  97. package/dist/commands/reports.js +6 -62
  98. package/dist/commands/salary-types.d.ts +0 -1
  99. package/dist/commands/salary-types.js +5 -61
  100. package/dist/commands/sales-invoices.d.ts +0 -1
  101. package/dist/commands/sales-invoices.js +12 -65
  102. package/dist/commands/sales-orders.d.ts +0 -1
  103. package/dist/commands/sales-orders.js +7 -63
  104. package/dist/commands/saved-views.d.ts +0 -1
  105. package/dist/commands/saved-views.js +5 -61
  106. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  107. package/dist/commands/scheduled-processor-mappings.js +5 -61
  108. package/dist/commands/schedules.d.ts +0 -1
  109. package/dist/commands/schedules.js +5 -61
  110. package/dist/commands/switch.js +4 -4
  111. package/dist/commands/tag-configurations.d.ts +0 -1
  112. package/dist/commands/tag-configurations.js +9 -64
  113. package/dist/commands/tags.d.ts +0 -1
  114. package/dist/commands/tags.js +5 -61
  115. package/dist/commands/tax-codes.d.ts +0 -1
  116. package/dist/commands/tax-codes.js +5 -61
  117. package/dist/commands/tax-matrix.d.ts +0 -1
  118. package/dist/commands/tax-matrix.js +5 -61
  119. package/dist/commands/tenants.d.ts +0 -1
  120. package/dist/commands/tenants.js +7 -62
  121. package/dist/commands/text-templates.d.ts +0 -1
  122. package/dist/commands/text-templates.js +5 -61
  123. package/dist/commands/tools.d.ts +0 -1
  124. package/dist/commands/tools.js +5 -61
  125. package/dist/commands/vat-declarations.d.ts +1 -1
  126. package/dist/commands/vat-declarations.js +11 -62
  127. package/dist/commands/vendor-invoices.d.ts +0 -1
  128. package/dist/commands/vendor-invoices.js +10 -65
  129. package/dist/commands/webhooks.d.ts +0 -1
  130. package/dist/commands/webhooks.js +5 -61
  131. package/dist/commands/workflows.d.ts +0 -1
  132. package/dist/commands/workflows.js +5 -61
  133. package/dist/lib/api-client.js +3 -6
  134. package/dist/lib/auth/constants.d.ts +11 -0
  135. package/dist/lib/auth/constants.js +11 -0
  136. package/dist/lib/auth/errors.d.ts +17 -0
  137. package/dist/lib/auth/errors.js +22 -0
  138. package/dist/lib/auth/flow.d.ts +27 -0
  139. package/dist/lib/auth/flow.js +163 -0
  140. package/dist/lib/auth/guards.d.ts +5 -0
  141. package/dist/lib/auth/guards.js +14 -0
  142. package/dist/lib/auth/http.d.ts +16 -0
  143. package/dist/lib/auth/http.js +49 -0
  144. package/dist/lib/auth/index.d.ts +49 -0
  145. package/dist/lib/auth/index.js +217 -0
  146. package/dist/lib/auth/keycloak-provider.d.ts +75 -0
  147. package/dist/lib/auth/keycloak-provider.js +215 -0
  148. package/dist/lib/auth/pages.d.ts +25 -0
  149. package/dist/lib/auth/pages.js +123 -0
  150. package/dist/lib/auth/pkce.d.ts +11 -0
  151. package/dist/lib/auth/pkce.js +17 -0
  152. package/dist/lib/auth/tokens.d.ts +54 -0
  153. package/dist/lib/auth/tokens.js +104 -0
  154. package/dist/lib/auth/types.d.ts +57 -0
  155. package/dist/lib/auth/types.js +1 -0
  156. package/dist/lib/auth/validated-url.d.ts +6 -0
  157. package/dist/lib/auth/validated-url.js +53 -0
  158. package/dist/lib/config.d.ts +13 -3
  159. package/dist/lib/config.js +24 -19
  160. package/oclif.manifest.json +440 -71
  161. package/package.json +2 -1
  162. package/dist/lib/auth.d.ts +0 -43
  163. package/dist/lib/auth.js +0 -610
package/README.md CHANGED
@@ -4,8 +4,8 @@ Command-line interface for Aderis Finance ERP. Manage your Aderis account, execu
4
4
 
5
5
  ## Features
6
6
 
7
- - **OAuth Authentication** - Secure login with PKCE flow
8
- - **Multi-Environment Support** - Switch between dev, test, and production environments
7
+ - **OAuth Authentication** - Secure browser login with PKCE flow, through Keycloak
8
+ - **Multi-Environment Support** - Switch between dev, test, staging and production environments
9
9
  - **Multi-Client Support** - Manage multiple clients with named contexts
10
10
  - **Multi-Tenant Support** - Switch between tenants within a single login
11
11
  - **Full API Access** - Execute all Aderis API endpoints from the command line
@@ -104,6 +104,26 @@ aderis auth login --context my-client
104
104
  aderis auth login --new --name my-client --environment dev --client-id YOUR_CLIENT_ID
105
105
  ```
106
106
 
107
+ ### Logging in via Keycloak
108
+
109
+ Keycloak is the only identity provider; an OAuth context needs no provider flag:
110
+
111
+ ```bash
112
+ aderis config add --environment test --name test-kc
113
+ aderis auth login --context test-kc
114
+ ```
115
+
116
+ The CLI logs in with the public `aderis-cli-login` client (Authorization Code + PKCE, `state` + `nonce`, scopes `openid profile email offline_access`) against the realm of the context's environment:
117
+
118
+ | Environment | Keycloak host | Realm |
119
+ |-------------|---------------|-------|
120
+ | dev | https://iam-dev.aderis.com | dev |
121
+ | test | https://iam-test.aderis.com | test |
122
+ | staging | https://iam-staging.aderis.com | staging |
123
+ | live | https://iam.aderis.com | prod |
124
+
125
+ Tenants come from the token (`tenantId`, `availableTenants`); switching is one refresh-token request carrying `tenant_id=<uuid>`, with no browser. `--keycloak-url <https origin>` on `config add` points a context at another allowlisted Keycloak host, e.g. `https://aderis-erp-nonprod.cloud-iam.com`. Requires [infrastructure-stacks#152](https://github.com/AderisERP/infrastructure-stacks/issues/152) for tokens from the `iam-<env>.aderis.com` hosts to be accepted by the API. See [docs/getting-started/authentication.md](docs/getting-started/authentication.md#logging-in-via-keycloak).
126
+
107
127
  ### Logout
108
128
 
109
129
  ```bash
@@ -216,12 +236,14 @@ Configuration is stored in `~/.aderis/config.json`.
216
236
 
217
237
  ### Environments
218
238
 
219
- | Environment | Auth URL | API URL |
220
- |-------------|----------|---------|
221
- | dev | https://auth-dev.aderis.com | https://api-dev.aderis.com |
222
- | test | https://auth-test.aderis.com | https://api-test.aderis.com |
223
- | staging | https://auth-staging.aderis.com | https://api-staging.aderis.com |
224
- | live | https://auth.aderis.com | https://api.aderis.com |
239
+ | Environment | API URL | Keycloak host / realm |
240
+ |-------------|---------|-----------------------|
241
+ | dev | https://api-dev.aderis.com | https://iam-dev.aderis.com / dev |
242
+ | test | https://api-test.aderis.com | https://iam-test.aderis.com / test |
243
+ | staging | https://api-staging.aderis.com | https://iam-staging.aderis.com / staging |
244
+ | live | https://api.aderis.com | https://iam.aderis.com / prod |
245
+
246
+ Set `ADERIS_CONFIG_DIR` (an absolute path) to keep `config.json` somewhere other than `~/.aderis`.
225
247
 
226
248
  ## Development
227
249
 
@@ -31,7 +31,6 @@ export default class AccessGroups 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;
@@ -43,7 +43,7 @@ export default class AccessGroups extends Command {
43
43
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
44
44
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
45
45
  query: Flags.string({ description: 'Additional query params as JSON' }),
46
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
46
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
47
47
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
48
48
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
49
49
  };
@@ -115,6 +115,8 @@ export default class AccessGroups extends Command {
115
115
  queryParams.search = flags.search;
116
116
  if (flags['sort-by'])
117
117
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
118
+ if (flags.fields)
119
+ queryParams.fields = flags.fields;
118
120
  if (flags.query) {
119
121
  try {
120
122
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -317,11 +319,6 @@ export default class AccessGroups extends Command {
317
319
  output = obj[arrayKeys[0]];
318
320
  }
319
321
  }
320
- // Filter fields if --fields flag is provided
321
- if (flags.fields) {
322
- const fieldList = flags.fields.split(',').map(f => f.trim());
323
- output = this.filterFields(output, fieldList);
324
- }
325
322
  // Sanitize output if requested
326
323
  if (flags.sanitize) {
327
324
  output = this.sanitizeOutput(output);
@@ -576,6 +573,8 @@ export default class AccessGroups extends Command {
576
573
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
577
574
  if (flags.include)
578
575
  baseParams.include = flags.include;
576
+ if (flags.fields)
577
+ baseParams.fields = flags.fields;
579
578
  if (flags.query) {
580
579
  try {
581
580
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -630,12 +629,7 @@ export default class AccessGroups extends Command {
630
629
  }
631
630
  }
632
631
  process.stderr.write(' done\n');
633
- // Filter fields if specified
634
632
  let output = allItems;
635
- if (flags.fields) {
636
- const fieldList = flags.fields.split(',').map(f => f.trim());
637
- output = this.filterFields(allItems, fieldList);
638
- }
639
633
  // Sanitize output if requested
640
634
  if (flags.sanitize) {
641
635
  output = this.sanitizeOutput(output);
@@ -964,56 +958,6 @@ export default class AccessGroups extends Command {
964
958
  return `\$${op}`;
965
959
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
966
960
  }
967
- filterFields(data, fields) {
968
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
969
- const pick = (obj, keys) => {
970
- // Group keys by their first segment so sibling sub-paths
971
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
972
- // and don't clobber each other on merge.
973
- const directKeys = new Set();
974
- const nestedKeys = new Map();
975
- for (const key of keys) {
976
- if (key.split('.').some(isUnsafeKey))
977
- continue;
978
- if (key.includes('.')) {
979
- const [first, ...rest] = key.split('.');
980
- if (isUnsafeKey(first))
981
- continue;
982
- const list = nestedKeys.get(first);
983
- if (list) {
984
- list.push(rest.join('.'));
985
- }
986
- else {
987
- nestedKeys.set(first, [rest.join('.')]);
988
- }
989
- }
990
- else {
991
- directKeys.add(key);
992
- }
993
- }
994
- const result = {};
995
- for (const key of directKeys) {
996
- if (key in obj)
997
- result[key] = obj[key];
998
- }
999
- for (const [first, restKeys] of nestedKeys) {
1000
- const val = obj[first];
1001
- if (!val || typeof val !== 'object')
1002
- continue;
1003
- if (Array.isArray(val)) {
1004
- result[first] = val.map(item => pick(item, restKeys));
1005
- }
1006
- else {
1007
- result[first] = pick(val, restKeys);
1008
- }
1009
- }
1010
- return result;
1011
- };
1012
- if (Array.isArray(data)) {
1013
- return data.map(item => pick(item, fields));
1014
- }
1015
- return pick(data, fields);
1016
- }
1017
961
  sanitizeOutput(data) {
1018
962
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1019
963
  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 AccountTransactionAllocations 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;
@@ -37,7 +37,7 @@ export default class AccountTransactionAllocations extends Command {
37
37
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
38
38
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
39
39
  query: Flags.string({ description: 'Additional query params as JSON' }),
40
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
40
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
41
41
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
42
42
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
43
43
  };
@@ -109,6 +109,8 @@ export default class AccountTransactionAllocations extends Command {
109
109
  queryParams.search = flags.search;
110
110
  if (flags['sort-by'])
111
111
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
112
+ if (flags.fields)
113
+ queryParams.fields = flags.fields;
112
114
  if (flags.query) {
113
115
  try {
114
116
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -311,11 +313,6 @@ export default class AccountTransactionAllocations extends Command {
311
313
  output = obj[arrayKeys[0]];
312
314
  }
313
315
  }
314
- // Filter fields if --fields flag is provided
315
- if (flags.fields) {
316
- const fieldList = flags.fields.split(',').map(f => f.trim());
317
- output = this.filterFields(output, fieldList);
318
- }
319
316
  // Sanitize output if requested
320
317
  if (flags.sanitize) {
321
318
  output = this.sanitizeOutput(output);
@@ -570,6 +567,8 @@ export default class AccountTransactionAllocations extends Command {
570
567
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
571
568
  if (flags.include)
572
569
  baseParams.include = flags.include;
570
+ if (flags.fields)
571
+ baseParams.fields = flags.fields;
573
572
  if (flags.query) {
574
573
  try {
575
574
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -624,12 +623,7 @@ export default class AccountTransactionAllocations extends Command {
624
623
  }
625
624
  }
626
625
  process.stderr.write(' done\n');
627
- // Filter fields if specified
628
626
  let output = allItems;
629
- if (flags.fields) {
630
- const fieldList = flags.fields.split(',').map(f => f.trim());
631
- output = this.filterFields(allItems, fieldList);
632
- }
633
627
  // Sanitize output if requested
634
628
  if (flags.sanitize) {
635
629
  output = this.sanitizeOutput(output);
@@ -958,56 +952,6 @@ export default class AccountTransactionAllocations extends Command {
958
952
  return `\$${op}`;
959
953
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
960
954
  }
961
- filterFields(data, fields) {
962
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
963
- const pick = (obj, keys) => {
964
- // Group keys by their first segment so sibling sub-paths
965
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
966
- // and don't clobber each other on merge.
967
- const directKeys = new Set();
968
- const nestedKeys = new Map();
969
- for (const key of keys) {
970
- if (key.split('.').some(isUnsafeKey))
971
- continue;
972
- if (key.includes('.')) {
973
- const [first, ...rest] = key.split('.');
974
- if (isUnsafeKey(first))
975
- continue;
976
- const list = nestedKeys.get(first);
977
- if (list) {
978
- list.push(rest.join('.'));
979
- }
980
- else {
981
- nestedKeys.set(first, [rest.join('.')]);
982
- }
983
- }
984
- else {
985
- directKeys.add(key);
986
- }
987
- }
988
- const result = {};
989
- for (const key of directKeys) {
990
- if (key in obj)
991
- result[key] = obj[key];
992
- }
993
- for (const [first, restKeys] of nestedKeys) {
994
- const val = obj[first];
995
- if (!val || typeof val !== 'object')
996
- continue;
997
- if (Array.isArray(val)) {
998
- result[first] = val.map(item => pick(item, restKeys));
999
- }
1000
- else {
1001
- result[first] = pick(val, restKeys);
1002
- }
1003
- }
1004
- return result;
1005
- };
1006
- if (Array.isArray(data)) {
1007
- return data.map(item => pick(item, fields));
1008
- }
1009
- return pick(data, fields);
1010
- }
1011
955
  sanitizeOutput(data) {
1012
956
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1013
957
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -33,7 +33,6 @@ export default class AccountTransactions extends Command {
33
33
  private runCamt053Import;
34
34
  private runNestedImport;
35
35
  private parseFilterValue;
36
- private filterFields;
37
36
  private sanitizeOutput;
38
37
  private getColumns;
39
38
  private getValue;
@@ -6,13 +6,14 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = true;
8
8
  const NESTED_IMPORTS = ["allocations", "allocations-notes"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "allocations-reprocess-unbooked", "allocations-unbooked", "list-allocations", "create-allocations", "get-allocations", "update-allocations", "delete-allocations", "allocations-confirm", "import-camt053", "list-allocations-notes", "create-allocations-notes", "get-allocations-notes", "update-allocations-notes", "delete-allocations-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "reconcile", "allocations-reprocess-unbooked", "allocations-unbooked", "list-allocations", "create-allocations", "get-allocations", "update-allocations", "delete-allocations", "list-allocations-settlement-accounts", "allocations-confirm", "import-camt053", "list-allocations-notes", "create-allocations-notes", "get-allocations-notes", "update-allocations-notes", "delete-allocations-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/account-transactions', summary: 'Get all account transactions with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
12
  'create': { method: 'POST', path: '/v1/account-transactions', summary: 'Create a new account transaction', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocations'] },
13
13
  'get': { method: 'GET', path: '/v1/account-transactions/{id}', summary: 'Get an account transaction by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
14
  'update': { method: 'PATCH', path: '/v1/account-transactions/{id}', summary: 'Update an account transaction', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocations'] },
15
15
  'delete': { method: 'DELETE', path: '/v1/account-transactions/{id}', summary: 'Delete an account transaction', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'reconcile': { method: 'POST', path: '/v1/account-transactions/reconcile', summary: 'Send selected transactions to the reconciliation agent', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['transactionIds'] },
16
17
  'allocations-reprocess-unbooked': { method: 'POST', path: '/v1/account-transactions/allocations/reprocess-unbooked', summary: 'Internal — re-book or repair allocations stuck at CONFIRMED', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocationIds'] },
17
18
  'allocations-unbooked': { method: 'GET', path: '/v1/account-transactions/allocations/unbooked', summary: 'Internal — list allocations stuck at CONFIRMED', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
19
  'list-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Get all allocations for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
@@ -20,7 +21,8 @@ const ENDPOINTS = {
20
21
  'get-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Get an allocation by ID', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
22
  'update-allocations': { method: 'PATCH', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Update an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['notes'] },
22
23
  'delete-allocations': { method: 'DELETE', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Delete an allocation', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
- 'allocations-confirm': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/confirm', summary: 'Confirm an allocation (transition from REVIEW to CONFIRMED)', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
+ 'list-allocations-settlement-accounts': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/settlement-accounts', summary: 'Which account each ledger would settle the allocated document against', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
+ 'allocations-confirm': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}/confirm', summary: 'Confirm an allocation (transition from REVIEW to CONFIRMED)', pathParams: ['transactionId', 'allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['counterAccounts'] },
24
26
  'import-camt053': { method: 'POST', path: '/v1/account-transactions/import/camt053', summary: 'Import CAMT.053 bank statement (ISO 20022 format)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
27
  'list-allocations-notes': { method: 'GET', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Get all notes for an allocation', pathParams: ['allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
28
  'create-allocations-notes': { method: 'POST', path: '/v1/account-transactions/allocations/{allocationId}/notes', summary: 'Create a note for an allocation', pathParams: ['allocationId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
@@ -59,7 +61,7 @@ export default class AccountTransactions extends Command {
59
61
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
60
62
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
61
63
  query: Flags.string({ description: 'Additional query params as JSON' }),
62
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
64
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
63
65
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
64
66
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
65
67
  };
@@ -131,6 +133,8 @@ export default class AccountTransactions extends Command {
131
133
  queryParams.search = flags.search;
132
134
  if (flags['sort-by'])
133
135
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
136
+ if (flags.fields)
137
+ queryParams.fields = flags.fields;
134
138
  if (flags.query) {
135
139
  try {
136
140
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -333,11 +337,6 @@ export default class AccountTransactions extends Command {
333
337
  output = obj[arrayKeys[0]];
334
338
  }
335
339
  }
336
- // Filter fields if --fields flag is provided
337
- if (flags.fields) {
338
- const fieldList = flags.fields.split(',').map(f => f.trim());
339
- output = this.filterFields(output, fieldList);
340
- }
341
340
  // Sanitize output if requested
342
341
  if (flags.sanitize) {
343
342
  output = this.sanitizeOutput(output);
@@ -592,6 +591,8 @@ export default class AccountTransactions extends Command {
592
591
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
593
592
  if (flags.include)
594
593
  baseParams.include = flags.include;
594
+ if (flags.fields)
595
+ baseParams.fields = flags.fields;
595
596
  if (flags.query) {
596
597
  try {
597
598
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -646,12 +647,7 @@ export default class AccountTransactions extends Command {
646
647
  }
647
648
  }
648
649
  process.stderr.write(' done\n');
649
- // Filter fields if specified
650
650
  let output = allItems;
651
- if (flags.fields) {
652
- const fieldList = flags.fields.split(',').map(f => f.trim());
653
- output = this.filterFields(allItems, fieldList);
654
- }
655
651
  // Sanitize output if requested
656
652
  if (flags.sanitize) {
657
653
  output = this.sanitizeOutput(output);
@@ -980,56 +976,6 @@ export default class AccountTransactions extends Command {
980
976
  return `\$${op}`;
981
977
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
982
978
  }
983
- filterFields(data, fields) {
984
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
985
- const pick = (obj, keys) => {
986
- // Group keys by their first segment so sibling sub-paths
987
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
988
- // and don't clobber each other on merge.
989
- const directKeys = new Set();
990
- const nestedKeys = new Map();
991
- for (const key of keys) {
992
- if (key.split('.').some(isUnsafeKey))
993
- continue;
994
- if (key.includes('.')) {
995
- const [first, ...rest] = key.split('.');
996
- if (isUnsafeKey(first))
997
- continue;
998
- const list = nestedKeys.get(first);
999
- if (list) {
1000
- list.push(rest.join('.'));
1001
- }
1002
- else {
1003
- nestedKeys.set(first, [rest.join('.')]);
1004
- }
1005
- }
1006
- else {
1007
- directKeys.add(key);
1008
- }
1009
- }
1010
- const result = {};
1011
- for (const key of directKeys) {
1012
- if (key in obj)
1013
- result[key] = obj[key];
1014
- }
1015
- for (const [first, restKeys] of nestedKeys) {
1016
- const val = obj[first];
1017
- if (!val || typeof val !== 'object')
1018
- continue;
1019
- if (Array.isArray(val)) {
1020
- result[first] = val.map(item => pick(item, restKeys));
1021
- }
1022
- else {
1023
- result[first] = pick(val, restKeys);
1024
- }
1025
- }
1026
- return result;
1027
- };
1028
- if (Array.isArray(data)) {
1029
- return data.map(item => pick(item, fields));
1030
- }
1031
- return pick(data, fields);
1032
- }
1033
979
  sanitizeOutput(data) {
1034
980
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1035
981
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -32,7 +32,6 @@ export default class Accounts extends Command {
32
32
  private runCamt053Import;
33
33
  private runNestedImport;
34
34
  private parseFilterValue;
35
- private filterFields;
36
35
  private sanitizeOutput;
37
36
  private getColumns;
38
37
  private getValue;
@@ -49,7 +49,7 @@ export default class Accounts extends Command {
49
49
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
50
50
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
51
51
  query: Flags.string({ description: 'Additional query params as JSON' }),
52
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
52
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
53
53
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
54
54
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
55
55
  };
@@ -121,6 +121,8 @@ export default class Accounts extends Command {
121
121
  queryParams.search = flags.search;
122
122
  if (flags['sort-by'])
123
123
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
124
+ if (flags.fields)
125
+ queryParams.fields = flags.fields;
124
126
  if (flags.query) {
125
127
  try {
126
128
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -323,11 +325,6 @@ export default class Accounts extends Command {
323
325
  output = obj[arrayKeys[0]];
324
326
  }
325
327
  }
326
- // Filter fields if --fields flag is provided
327
- if (flags.fields) {
328
- const fieldList = flags.fields.split(',').map(f => f.trim());
329
- output = this.filterFields(output, fieldList);
330
- }
331
328
  // Sanitize output if requested
332
329
  if (flags.sanitize) {
333
330
  output = this.sanitizeOutput(output);
@@ -582,6 +579,8 @@ export default class Accounts extends Command {
582
579
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
583
580
  if (flags.include)
584
581
  baseParams.include = flags.include;
582
+ if (flags.fields)
583
+ baseParams.fields = flags.fields;
585
584
  if (flags.query) {
586
585
  try {
587
586
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -636,12 +635,7 @@ export default class Accounts extends Command {
636
635
  }
637
636
  }
638
637
  process.stderr.write(' done\n');
639
- // Filter fields if specified
640
638
  let output = allItems;
641
- if (flags.fields) {
642
- const fieldList = flags.fields.split(',').map(f => f.trim());
643
- output = this.filterFields(allItems, fieldList);
644
- }
645
639
  // Sanitize output if requested
646
640
  if (flags.sanitize) {
647
641
  output = this.sanitizeOutput(output);
@@ -970,56 +964,6 @@ export default class Accounts extends Command {
970
964
  return `\$${op}`;
971
965
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
972
966
  }
973
- filterFields(data, fields) {
974
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
975
- const pick = (obj, keys) => {
976
- // Group keys by their first segment so sibling sub-paths
977
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
978
- // and don't clobber each other on merge.
979
- const directKeys = new Set();
980
- const nestedKeys = new Map();
981
- for (const key of keys) {
982
- if (key.split('.').some(isUnsafeKey))
983
- continue;
984
- if (key.includes('.')) {
985
- const [first, ...rest] = key.split('.');
986
- if (isUnsafeKey(first))
987
- continue;
988
- const list = nestedKeys.get(first);
989
- if (list) {
990
- list.push(rest.join('.'));
991
- }
992
- else {
993
- nestedKeys.set(first, [rest.join('.')]);
994
- }
995
- }
996
- else {
997
- directKeys.add(key);
998
- }
999
- }
1000
- const result = {};
1001
- for (const key of directKeys) {
1002
- if (key in obj)
1003
- result[key] = obj[key];
1004
- }
1005
- for (const [first, restKeys] of nestedKeys) {
1006
- const val = obj[first];
1007
- if (!val || typeof val !== 'object')
1008
- continue;
1009
- if (Array.isArray(val)) {
1010
- result[first] = val.map(item => pick(item, restKeys));
1011
- }
1012
- else {
1013
- result[first] = pick(val, restKeys);
1014
- }
1015
- }
1016
- return result;
1017
- };
1018
- if (Array.isArray(data)) {
1019
- return data.map(item => pick(item, fields));
1020
- }
1021
- return pick(data, fields);
1022
- }
1023
967
  sanitizeOutput(data) {
1024
968
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1025
969
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -32,7 +32,6 @@ export default class AgentPlatform extends Command {
32
32
  private runCamt053Import;
33
33
  private runNestedImport;
34
34
  private parseFilterValue;
35
- private filterFields;
36
35
  private sanitizeOutput;
37
36
  private getColumns;
38
37
  private getValue;