@aderis-erp/cli 1.1.0 → 1.1.2

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 (152) hide show
  1. package/dist/commands/access-groups.d.ts +8 -0
  2. package/dist/commands/access-groups.js +56 -8
  3. package/dist/commands/account-transaction-allocations.d.ts +8 -0
  4. package/dist/commands/account-transaction-allocations.js +52 -5
  5. package/dist/commands/account-transactions.d.ts +8 -0
  6. package/dist/commands/account-transactions.js +55 -7
  7. package/dist/commands/accounts.d.ts +8 -0
  8. package/dist/commands/accounts.js +52 -5
  9. package/dist/commands/agent-platform.d.ts +8 -0
  10. package/dist/commands/agent-platform.js +52 -5
  11. package/dist/commands/agent-runs.d.ts +8 -0
  12. package/dist/commands/agent-runs.js +52 -5
  13. package/dist/commands/analytics.d.ts +8 -0
  14. package/dist/commands/analytics.js +52 -5
  15. package/dist/commands/api-keys.d.ts +8 -0
  16. package/dist/commands/api-keys.js +52 -5
  17. package/dist/commands/async-jobs.d.ts +8 -0
  18. package/dist/commands/async-jobs.js +52 -5
  19. package/dist/commands/audit-log.d.ts +8 -0
  20. package/dist/commands/audit-log.js +52 -5
  21. package/dist/commands/bank-accounts.d.ts +8 -0
  22. package/dist/commands/bank-accounts.js +52 -5
  23. package/dist/commands/bank-integrations.d.ts +8 -0
  24. package/dist/commands/bank-integrations.js +52 -5
  25. package/dist/commands/business-partners.d.ts +9 -0
  26. package/dist/commands/business-partners.js +66 -9
  27. package/dist/commands/chat.d.ts +8 -0
  28. package/dist/commands/chat.js +52 -5
  29. package/dist/commands/credit-card-accounts.d.ts +8 -0
  30. package/dist/commands/credit-card-accounts.js +52 -5
  31. package/dist/commands/delivery-notes.d.ts +8 -0
  32. package/dist/commands/delivery-notes.js +52 -5
  33. package/dist/commands/dunning-configs.d.ts +8 -0
  34. package/dist/commands/dunning-configs.js +52 -5
  35. package/dist/commands/dunning-reminders.d.ts +8 -0
  36. package/dist/commands/dunning-reminders.js +52 -5
  37. package/dist/commands/dunning-runs.d.ts +8 -0
  38. package/dist/commands/dunning-runs.js +52 -5
  39. package/dist/commands/email-transactions.d.ts +8 -0
  40. package/dist/commands/email-transactions.js +52 -5
  41. package/dist/commands/employees.d.ts +8 -0
  42. package/dist/commands/employees.js +52 -5
  43. package/dist/commands/entity-configurations.d.ts +8 -0
  44. package/dist/commands/entity-configurations.js +52 -5
  45. package/dist/commands/event-processor-mappings.d.ts +8 -0
  46. package/dist/commands/event-processor-mappings.js +52 -5
  47. package/dist/commands/files.d.ts +8 -0
  48. package/dist/commands/files.js +53 -6
  49. package/dist/commands/health.d.ts +8 -0
  50. package/dist/commands/health.js +52 -5
  51. package/dist/commands/imports.d.ts +8 -0
  52. package/dist/commands/imports.js +53 -8
  53. package/dist/commands/inbox.d.ts +8 -0
  54. package/dist/commands/inbox.js +53 -7
  55. package/dist/commands/integration-identities.d.ts +8 -0
  56. package/dist/commands/integration-identities.js +52 -5
  57. package/dist/commands/integrations.d.ts +8 -0
  58. package/dist/commands/integrations.js +52 -5
  59. package/dist/commands/item-categories.d.ts +8 -0
  60. package/dist/commands/item-categories.js +52 -5
  61. package/dist/commands/items.d.ts +8 -0
  62. package/dist/commands/items.js +52 -5
  63. package/dist/commands/journal-lines.d.ts +8 -0
  64. package/dist/commands/journal-lines.js +52 -5
  65. package/dist/commands/journals.d.ts +8 -0
  66. package/dist/commands/journals.js +52 -5
  67. package/dist/commands/ledgers.d.ts +8 -0
  68. package/dist/commands/ledgers.js +57 -9
  69. package/dist/commands/my-account.d.ts +8 -0
  70. package/dist/commands/my-account.js +52 -5
  71. package/dist/commands/office-locations.d.ts +8 -0
  72. package/dist/commands/office-locations.js +52 -5
  73. package/dist/commands/payment-methods.d.ts +8 -0
  74. package/dist/commands/payment-methods.js +52 -5
  75. package/dist/commands/payment-runs.d.ts +8 -0
  76. package/dist/commands/payment-runs.js +54 -6
  77. package/dist/commands/payment-terms.d.ts +8 -0
  78. package/dist/commands/payment-terms.js +52 -5
  79. package/dist/commands/payments.d.ts +8 -0
  80. package/dist/commands/payments.js +52 -5
  81. package/dist/commands/payroll-runs.d.ts +8 -0
  82. package/dist/commands/payroll-runs.js +52 -5
  83. package/dist/commands/permissions.d.ts +8 -0
  84. package/dist/commands/permissions.js +52 -5
  85. package/dist/commands/price-lists.d.ts +8 -0
  86. package/dist/commands/price-lists.js +52 -5
  87. package/dist/commands/processor-mappings.d.ts +8 -0
  88. package/dist/commands/processor-mappings.js +52 -5
  89. package/dist/commands/processors.d.ts +8 -0
  90. package/dist/commands/processors.js +52 -5
  91. package/dist/commands/purchase-order-types.d.ts +8 -0
  92. package/dist/commands/purchase-order-types.js +52 -5
  93. package/dist/commands/purchase-orders.d.ts +8 -0
  94. package/dist/commands/purchase-orders.js +52 -5
  95. package/dist/commands/reports.d.ts +8 -0
  96. package/dist/commands/reports.js +52 -5
  97. package/dist/commands/roles.d.ts +8 -0
  98. package/dist/commands/roles.js +52 -5
  99. package/dist/commands/salary-types.d.ts +8 -0
  100. package/dist/commands/salary-types.js +52 -5
  101. package/dist/commands/sales-invoices.d.ts +10 -0
  102. package/dist/commands/sales-invoices.js +62 -6
  103. package/dist/commands/sales-orders.d.ts +8 -0
  104. package/dist/commands/sales-orders.js +52 -5
  105. package/dist/commands/saved-views.d.ts +8 -0
  106. package/dist/commands/saved-views.js +52 -5
  107. package/dist/commands/scheduled-processor-mappings.d.ts +8 -0
  108. package/dist/commands/scheduled-processor-mappings.js +52 -5
  109. package/dist/commands/schedules.d.ts +8 -0
  110. package/dist/commands/schedules.js +52 -5
  111. package/dist/commands/support/end.d.ts +9 -0
  112. package/dist/commands/support/end.js +29 -0
  113. package/dist/commands/support/start.d.ts +14 -0
  114. package/dist/commands/support/start.js +50 -0
  115. package/dist/commands/support/status.d.ts +10 -0
  116. package/dist/commands/support/status.js +45 -0
  117. package/dist/commands/support/tenants.d.ts +10 -0
  118. package/dist/commands/support/tenants.js +39 -0
  119. package/dist/commands/tags.d.ts +8 -0
  120. package/dist/commands/tags.js +56 -8
  121. package/dist/commands/tax-codes.d.ts +8 -0
  122. package/dist/commands/tax-codes.js +52 -5
  123. package/dist/commands/tax-matrix.d.ts +8 -0
  124. package/dist/commands/tax-matrix.js +52 -5
  125. package/dist/commands/templates.d.ts +8 -0
  126. package/dist/commands/templates.js +52 -5
  127. package/dist/commands/tenants.d.ts +8 -0
  128. package/dist/commands/tenants.js +52 -5
  129. package/dist/commands/tools.d.ts +8 -0
  130. package/dist/commands/tools.js +52 -5
  131. package/dist/commands/users.d.ts +8 -0
  132. package/dist/commands/users.js +52 -5
  133. package/dist/commands/vat-declarations.d.ts +8 -0
  134. package/dist/commands/vat-declarations.js +52 -5
  135. package/dist/commands/vendor-invoices.d.ts +9 -0
  136. package/dist/commands/vendor-invoices.js +60 -8
  137. package/dist/commands/webhooks.d.ts +8 -0
  138. package/dist/commands/webhooks.js +52 -5
  139. package/dist/commands/workflows.d.ts +8 -0
  140. package/dist/commands/workflows.js +52 -5
  141. package/dist/lib/auth/constants.d.ts +12 -0
  142. package/dist/lib/auth/constants.js +12 -0
  143. package/dist/lib/auth/index.d.ts +11 -1
  144. package/dist/lib/auth/index.js +90 -2
  145. package/dist/lib/auth/keycloak-provider.d.ts +22 -0
  146. package/dist/lib/auth/keycloak-provider.js +108 -3
  147. package/dist/lib/config.d.ts +18 -0
  148. package/dist/lib/config.js +10 -0
  149. package/oclif.manifest.json +640 -216
  150. package/package.json +4 -1
  151. package/dist/commands/tag-configurations.d.ts +0 -39
  152. package/dist/commands/tag-configurations.js +0 -1155
@@ -39,6 +39,7 @@ export default class SavedViews extends Command {
39
39
  query: Flags.string({ description: 'Additional query params as JSON' }),
40
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
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
42
43
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
43
44
  };
44
45
  async run() {
@@ -331,9 +332,15 @@ export default class SavedViews extends Command {
331
332
  }
332
333
  catch { /* keep as string */ }
333
334
  }
334
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
335
+ // A paginated answer is { data, meta, links, totals }. The records are
336
+ // unwrapped for output, but what the API said about the page is kept:
337
+ // dropping it silently let a script read one page as the whole result
338
+ // (AderisERP/product#61).
339
+ let envelope;
335
340
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
336
- output = output.data;
341
+ const page = output;
342
+ envelope = page;
343
+ output = page.data;
337
344
  }
338
345
  // For table/csv on non-array responses, auto-extract the primary array
339
346
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -350,12 +357,24 @@ export default class SavedViews extends Command {
350
357
  output = this.sanitizeOutput(output);
351
358
  }
352
359
  const fieldList = flags.fields?.split(',').map(f => f.trim());
353
- if (flags.csv || flags.output === 'csv')
360
+ if (envelope)
361
+ this.warnShortPage(envelope, flags.limit);
362
+ if (flags.csv || flags.output === 'csv') {
354
363
  this.printCsv(output, fieldList);
355
- else if (flags.table || flags.output === 'table')
364
+ }
365
+ else if (flags.table || flags.output === 'table') {
356
366
  this.printTable(output, fieldList);
357
- else
367
+ if (envelope)
368
+ this.printPageSummary(envelope);
369
+ }
370
+ else if (envelope && flags.meta) {
371
+ // The whole envelope, for a caller that asked for it; the default stays
372
+ // a bare array so existing scripts are unaffected.
373
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
374
+ }
375
+ else {
358
376
  this.log(JSON.stringify(output, null, 2));
377
+ }
359
378
  }
360
379
  catch (error) {
361
380
  if (error instanceof ApiError) {
@@ -984,6 +1003,34 @@ export default class SavedViews extends Command {
984
1003
  return `\$${op}`;
985
1004
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
986
1005
  }
1006
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1007
+ printPageSummary(envelope) {
1008
+ const meta = envelope.meta;
1009
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1010
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1011
+ this.log(`
1012
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1013
+ }
1014
+ for (const total of envelope.totals ?? []) {
1015
+ const { currency, ...sums } = total;
1016
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1017
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1018
+ }
1019
+ }
1020
+ /**
1021
+ * The API caps how many records a page may hold. Asking for more and getting
1022
+ * fewer looks like "that is everything" unless it is said out loud.
1023
+ */
1024
+ warnShortPage(envelope, requestedLimit) {
1025
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1026
+ const perPage = envelope.meta?.itemsPerPage;
1027
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1028
+ return;
1029
+ const pages = envelope.meta?.totalPages;
1030
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1031
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1032
+ `);
1033
+ }
987
1034
  sanitizeOutput(data) {
988
1035
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
989
1036
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -23,6 +23,7 @@ export default class ScheduledProcessorMappings extends Command {
23
23
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
24
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
25
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
27
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
28
  };
28
29
  run(): Promise<void>;
@@ -31,6 +32,13 @@ export default class ScheduledProcessorMappings extends Command {
31
32
  private runCamt053Import;
32
33
  private runNestedImport;
33
34
  private parseFilterValue;
35
+ /** The page line under a table: which page, how many, and any sums the API returned. */
36
+ private printPageSummary;
37
+ /**
38
+ * The API caps how many records a page may hold. Asking for more and getting
39
+ * fewer looks like "that is everything" unless it is said out loud.
40
+ */
41
+ private warnShortPage;
34
42
  private sanitizeOutput;
35
43
  private getColumns;
36
44
  private getValue;
@@ -45,6 +45,7 @@ export default class ScheduledProcessorMappings extends Command {
45
45
  query: Flags.string({ description: 'Additional query params as JSON' }),
46
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
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
48
49
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
49
50
  };
50
51
  async run() {
@@ -337,9 +338,15 @@ export default class ScheduledProcessorMappings extends Command {
337
338
  }
338
339
  catch { /* keep as string */ }
339
340
  }
340
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
341
+ // A paginated answer is { data, meta, links, totals }. The records are
342
+ // unwrapped for output, but what the API said about the page is kept:
343
+ // dropping it silently let a script read one page as the whole result
344
+ // (AderisERP/product#61).
345
+ let envelope;
341
346
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
342
- output = output.data;
347
+ const page = output;
348
+ envelope = page;
349
+ output = page.data;
343
350
  }
344
351
  // For table/csv on non-array responses, auto-extract the primary array
345
352
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -356,12 +363,24 @@ export default class ScheduledProcessorMappings extends Command {
356
363
  output = this.sanitizeOutput(output);
357
364
  }
358
365
  const fieldList = flags.fields?.split(',').map(f => f.trim());
359
- if (flags.csv || flags.output === 'csv')
366
+ if (envelope)
367
+ this.warnShortPage(envelope, flags.limit);
368
+ if (flags.csv || flags.output === 'csv') {
360
369
  this.printCsv(output, fieldList);
361
- else if (flags.table || flags.output === 'table')
370
+ }
371
+ else if (flags.table || flags.output === 'table') {
362
372
  this.printTable(output, fieldList);
363
- else
373
+ if (envelope)
374
+ this.printPageSummary(envelope);
375
+ }
376
+ else if (envelope && flags.meta) {
377
+ // The whole envelope, for a caller that asked for it; the default stays
378
+ // a bare array so existing scripts are unaffected.
379
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
380
+ }
381
+ else {
364
382
  this.log(JSON.stringify(output, null, 2));
383
+ }
365
384
  }
366
385
  catch (error) {
367
386
  if (error instanceof ApiError) {
@@ -990,6 +1009,34 @@ export default class ScheduledProcessorMappings extends Command {
990
1009
  return `\$${op}`;
991
1010
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
992
1011
  }
1012
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1013
+ printPageSummary(envelope) {
1014
+ const meta = envelope.meta;
1015
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1016
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1017
+ this.log(`
1018
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1019
+ }
1020
+ for (const total of envelope.totals ?? []) {
1021
+ const { currency, ...sums } = total;
1022
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1023
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1024
+ }
1025
+ }
1026
+ /**
1027
+ * The API caps how many records a page may hold. Asking for more and getting
1028
+ * fewer looks like "that is everything" unless it is said out loud.
1029
+ */
1030
+ warnShortPage(envelope, requestedLimit) {
1031
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1032
+ const perPage = envelope.meta?.itemsPerPage;
1033
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1034
+ return;
1035
+ const pages = envelope.meta?.totalPages;
1036
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1037
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1038
+ `);
1039
+ }
993
1040
  sanitizeOutput(data) {
994
1041
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
995
1042
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -23,6 +23,7 @@ export default class Schedules extends Command {
23
23
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
24
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
25
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
27
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
28
  };
28
29
  run(): Promise<void>;
@@ -31,6 +32,13 @@ export default class Schedules extends Command {
31
32
  private runCamt053Import;
32
33
  private runNestedImport;
33
34
  private parseFilterValue;
35
+ /** The page line under a table: which page, how many, and any sums the API returned. */
36
+ private printPageSummary;
37
+ /**
38
+ * The API caps how many records a page may hold. Asking for more and getting
39
+ * fewer looks like "that is everything" unless it is said out loud.
40
+ */
41
+ private warnShortPage;
34
42
  private sanitizeOutput;
35
43
  private getColumns;
36
44
  private getValue;
@@ -49,6 +49,7 @@ export default class Schedules extends Command {
49
49
  query: Flags.string({ description: 'Additional query params as JSON' }),
50
50
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
51
51
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
52
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
52
53
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
53
54
  };
54
55
  async run() {
@@ -341,9 +342,15 @@ export default class Schedules extends Command {
341
342
  }
342
343
  catch { /* keep as string */ }
343
344
  }
344
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
345
+ // A paginated answer is { data, meta, links, totals }. The records are
346
+ // unwrapped for output, but what the API said about the page is kept:
347
+ // dropping it silently let a script read one page as the whole result
348
+ // (AderisERP/product#61).
349
+ let envelope;
345
350
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
346
- output = output.data;
351
+ const page = output;
352
+ envelope = page;
353
+ output = page.data;
347
354
  }
348
355
  // For table/csv on non-array responses, auto-extract the primary array
349
356
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -360,12 +367,24 @@ export default class Schedules extends Command {
360
367
  output = this.sanitizeOutput(output);
361
368
  }
362
369
  const fieldList = flags.fields?.split(',').map(f => f.trim());
363
- if (flags.csv || flags.output === 'csv')
370
+ if (envelope)
371
+ this.warnShortPage(envelope, flags.limit);
372
+ if (flags.csv || flags.output === 'csv') {
364
373
  this.printCsv(output, fieldList);
365
- else if (flags.table || flags.output === 'table')
374
+ }
375
+ else if (flags.table || flags.output === 'table') {
366
376
  this.printTable(output, fieldList);
367
- else
377
+ if (envelope)
378
+ this.printPageSummary(envelope);
379
+ }
380
+ else if (envelope && flags.meta) {
381
+ // The whole envelope, for a caller that asked for it; the default stays
382
+ // a bare array so existing scripts are unaffected.
383
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
384
+ }
385
+ else {
368
386
  this.log(JSON.stringify(output, null, 2));
387
+ }
369
388
  }
370
389
  catch (error) {
371
390
  if (error instanceof ApiError) {
@@ -994,6 +1013,34 @@ export default class Schedules extends Command {
994
1013
  return `\$${op}`;
995
1014
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
996
1015
  }
1016
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1017
+ printPageSummary(envelope) {
1018
+ const meta = envelope.meta;
1019
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1020
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1021
+ this.log(`
1022
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1023
+ }
1024
+ for (const total of envelope.totals ?? []) {
1025
+ const { currency, ...sums } = total;
1026
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1027
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1028
+ }
1029
+ }
1030
+ /**
1031
+ * The API caps how many records a page may hold. Asking for more and getting
1032
+ * fewer looks like "that is everything" unless it is said out loud.
1033
+ */
1034
+ warnShortPage(envelope, requestedLimit) {
1035
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1036
+ const perPage = envelope.meta?.itemsPerPage;
1037
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1038
+ return;
1039
+ const pages = envelope.meta?.totalPages;
1040
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1041
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1042
+ `);
1043
+ }
997
1044
  sanitizeOutput(data) {
998
1045
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
999
1046
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SupportEnd extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,29 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { endSupportSession } from '../../lib/auth/index.js';
3
+ import { getCurrentContext } from '../../lib/config.js';
4
+ export default class SupportEnd extends Command {
5
+ static description = 'Stop acting as Aderis Support and return to your own account';
6
+ static examples = ['<%= config.bin %> support end', '<%= config.bin %> support end --context prod'];
7
+ static flags = {
8
+ context: Flags.string({
9
+ aliases: ['config'],
10
+ char: 'c',
11
+ description: 'Context to use, overriding the current context (alias: --config)',
12
+ env: 'ADERIS_CONTEXT',
13
+ }),
14
+ };
15
+ async run() {
16
+ const { flags } = await this.parse(SupportEnd);
17
+ const contextName = flags.context ?? getCurrentContext()?.name;
18
+ if (!contextName) {
19
+ this.error('No context configured. Run: aderis auth login --new');
20
+ }
21
+ const ended = await endSupportSession(contextName);
22
+ if (!ended) {
23
+ this.log(`Context ${contextName} was not acting as Aderis Support.`);
24
+ return;
25
+ }
26
+ this.log(`Stopped acting as Aderis Support in ${ended.tenant_name}.`);
27
+ this.log(`Context ${contextName} is your own account again; no new login was needed.`);
28
+ }
29
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SupportStart extends Command {
3
+ static args: {
4
+ tenantId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ /** Without a tenant id, offer the customers Keycloak says this operator may act for. */
13
+ private chooseTenant;
14
+ }
@@ -0,0 +1,50 @@
1
+ import { select } from '@inquirer/prompts';
2
+ import { Args, Command, Flags } from '@oclif/core';
3
+ import { listSupportTenants, startSupportSession, tenantLabel } from '../../lib/auth/index.js';
4
+ import { getCurrentContext } from '../../lib/config.js';
5
+ export default class SupportStart extends Command {
6
+ static args = {
7
+ tenantId: Args.string({ description: 'Customer to act for; prompted for if omitted', required: false }),
8
+ };
9
+ static description = 'Act as Aderis Support in a customer tenant';
10
+ static examples = [
11
+ '<%= config.bin %> support start',
12
+ '<%= config.bin %> support start 11839c8b-7ec3-4cc1-9dae-396ced09b928',
13
+ '<%= config.bin %> support start <tenant-id> --context prod',
14
+ ];
15
+ static flags = {
16
+ context: Flags.string({
17
+ aliases: ['config'],
18
+ char: 'c',
19
+ description: 'Context to use, overriding the current context (alias: --config)',
20
+ env: 'ADERIS_CONTEXT',
21
+ }),
22
+ };
23
+ async run() {
24
+ const { args, flags } = await this.parse(SupportStart);
25
+ const contextName = flags.context ?? getCurrentContext()?.name;
26
+ if (!contextName) {
27
+ this.error('No context configured. Run: aderis auth login --new');
28
+ }
29
+ const tenantId = args.tenantId ?? (await this.chooseTenant(contextName));
30
+ const support = await startSupportSession(contextName, tenantId);
31
+ this.log(`Acting as Aderis Support in ${support.tenant_name} (${support.tenant_id})`);
32
+ this.log(`Context: ${contextName}`);
33
+ this.warn('Every call from this context now goes out as Aderis Support, and every write is recorded against you. ' +
34
+ 'End with: aderis support end');
35
+ }
36
+ /** Without a tenant id, offer the customers Keycloak says this operator may act for. */
37
+ async chooseTenant(contextName) {
38
+ const tenants = await listSupportTenants(contextName);
39
+ if (tenants.length === 0) {
40
+ this.error('There are no customers you may act for.');
41
+ }
42
+ if (!process.stdin.isTTY) {
43
+ this.error(`Name the customer to act for. List them with: aderis support tenants --context ${contextName}`);
44
+ }
45
+ return select({
46
+ choices: tenants.map((t) => ({ name: tenantLabel(t), value: t.tenantId })),
47
+ message: 'Act as Aderis Support in which customer?',
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SupportStatus extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,45 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getContext, getCurrentContext, getSupportSession } from '../../lib/config.js';
3
+ export default class SupportStatus extends Command {
4
+ static description = 'Show whether this context is acting as Aderis Support';
5
+ static examples = ['<%= config.bin %> support status', '<%= config.bin %> support status --context prod'];
6
+ static flags = {
7
+ context: Flags.string({
8
+ aliases: ['config'],
9
+ char: 'c',
10
+ description: 'Context to check, overriding the current context (alias: --config)',
11
+ env: 'ADERIS_CONTEXT',
12
+ }),
13
+ json: Flags.boolean({ default: false, description: 'Output as JSON' }),
14
+ };
15
+ async run() {
16
+ const { flags } = await this.parse(SupportStatus);
17
+ const contextName = flags.context ?? getCurrentContext()?.name;
18
+ if (!contextName) {
19
+ this.error('No context configured. Run: aderis auth login --new');
20
+ }
21
+ const context = getContext(contextName);
22
+ if (!context) {
23
+ this.error(`Context "${contextName}" not found.`);
24
+ }
25
+ const support = getSupportSession(context);
26
+ if (flags.json) {
27
+ this.log(JSON.stringify({ context: contextName, supportSession: support ? supportJson(support) : null }, null, 2));
28
+ return;
29
+ }
30
+ if (!support) {
31
+ this.log(`Context ${contextName} is acting as yourself.`);
32
+ return;
33
+ }
34
+ this.log(`Acting as Aderis Support in ${support.tenant_name} (${support.tenant_id})`);
35
+ this.log(`Context: ${contextName}`);
36
+ if (support.tokens.expires_at) {
37
+ const expires = new Date(support.tokens.expires_at);
38
+ this.log(`Token expires: ${expires.toLocaleString()}${expires < new Date() ? ' (refreshes on next call)' : ''}`);
39
+ }
40
+ this.log('\nEverything you do is recorded against you. End with: aderis support end');
41
+ }
42
+ }
43
+ function supportJson(support) {
44
+ return { expiresAt: support.tokens.expires_at ?? null, tenantId: support.tenant_id, tenantName: support.tenant_name };
45
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SupportTenants extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,39 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { listSupportTenants, tenantLabel } from '../../lib/auth/index.js';
3
+ import { getCurrentContext } from '../../lib/config.js';
4
+ export default class SupportTenants extends Command {
5
+ static description = 'List the customers you may act for as Aderis Support';
6
+ static examples = [
7
+ '<%= config.bin %> support tenants',
8
+ '<%= config.bin %> support tenants --context prod --json',
9
+ ];
10
+ static flags = {
11
+ context: Flags.string({
12
+ aliases: ['config'],
13
+ char: 'c',
14
+ description: 'Context to use, overriding the current context (alias: --config)',
15
+ env: 'ADERIS_CONTEXT',
16
+ }),
17
+ json: Flags.boolean({ default: false, description: 'Output as JSON' }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(SupportTenants);
21
+ const contextName = flags.context ?? getCurrentContext()?.name;
22
+ if (!contextName) {
23
+ this.error('No context configured. Run: aderis auth login --new');
24
+ }
25
+ const tenants = await listSupportTenants(contextName);
26
+ if (flags.json) {
27
+ this.log(JSON.stringify(tenants, null, 2));
28
+ return;
29
+ }
30
+ if (tenants.length === 0) {
31
+ this.log('No customers available to act for.');
32
+ return;
33
+ }
34
+ this.log('Customers you may act for:\n');
35
+ for (const tenant of tenants)
36
+ this.log(` ${tenantLabel(tenant)}`);
37
+ this.log(`\nStart with: aderis support start <tenant-id> --context ${contextName}`);
38
+ }
39
+ }
@@ -23,6 +23,7 @@ export default class Tags extends Command {
23
23
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
24
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
25
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
27
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
28
  };
28
29
  run(): Promise<void>;
@@ -31,6 +32,13 @@ export default class Tags extends Command {
31
32
  private runCamt053Import;
32
33
  private runNestedImport;
33
34
  private parseFilterValue;
35
+ /** The page line under a table: which page, how many, and any sums the API returned. */
36
+ private printPageSummary;
37
+ /**
38
+ * The API caps how many records a page may hold. Asking for more and getting
39
+ * fewer looks like "that is everything" unless it is said out loud.
40
+ */
41
+ private warnShortPage;
34
42
  private sanitizeOutput;
35
43
  private getColumns;
36
44
  private getValue;