@aderis-erp/cli 1.0.2 → 1.0.3

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 (85) hide show
  1. package/dist/commands/{processing-logs.d.ts → access-groups.d.ts} +1 -1
  2. package/dist/commands/{access-groups 2.js → access-groups.js} +30 -18
  3. package/dist/commands/account-transactions.d.ts +41 -0
  4. package/dist/commands/account-transactions.js +1032 -0
  5. package/dist/commands/accounts.d.ts +40 -0
  6. package/dist/commands/accounts.js +1024 -0
  7. package/dist/commands/agent-runs.d.ts +39 -0
  8. package/dist/commands/agent-runs.js +1013 -0
  9. package/dist/commands/{pubsub.d.ts → async-endpoints-internal.d.ts} +1 -1
  10. package/dist/commands/{pubsub.js → async-endpoints-internal.js} +37 -25
  11. package/dist/commands/audit-log.d.ts +39 -0
  12. package/dist/commands/{processing-logs.js → audit-log.js} +42 -28
  13. package/dist/commands/auth/login.d.ts +14 -0
  14. package/dist/commands/auth/login.js +116 -0
  15. package/dist/commands/auth/logout.d.ts +10 -0
  16. package/dist/commands/auth/logout.js +58 -0
  17. package/dist/commands/auth/status.d.ts +12 -0
  18. package/dist/commands/auth/status.js +101 -0
  19. package/dist/commands/auth/tenant.d.ts +19 -0
  20. package/dist/commands/auth/tenant.js +184 -0
  21. package/dist/commands/auth.d.ts +37 -0
  22. package/dist/commands/auth.js +871 -0
  23. package/dist/commands/bank-accounts.d.ts +41 -0
  24. package/dist/commands/bank-accounts.js +1032 -0
  25. package/dist/commands/bank-integrations.js +30 -18
  26. package/dist/commands/business-partners---contacts.js +30 -18
  27. package/dist/commands/business-partners.js +30 -18
  28. package/dist/commands/config/add.d.ts +13 -0
  29. package/dist/commands/config/add.js +120 -0
  30. package/dist/commands/config/current.d.ts +9 -0
  31. package/dist/commands/config/current.js +43 -0
  32. package/dist/commands/config/delete.d.ts +12 -0
  33. package/dist/commands/config/delete.js +67 -0
  34. package/dist/commands/config/list.d.ts +9 -0
  35. package/dist/commands/config/list.js +50 -0
  36. package/dist/commands/config/use.d.ts +9 -0
  37. package/dist/commands/config/use.js +40 -0
  38. package/dist/commands/credit-card-accounts.js +30 -18
  39. package/dist/commands/documents.js +30 -18
  40. package/dist/commands/dunning-configs.js +30 -18
  41. package/dist/commands/dunning-reminders.js +30 -18
  42. package/dist/commands/dunning-runs.js +30 -18
  43. package/dist/commands/employees.js +30 -18
  44. package/dist/commands/event-processor-mappings.js +30 -18
  45. package/dist/commands/files-internal.js +30 -18
  46. package/dist/commands/health.js +30 -18
  47. package/dist/commands/inbox.js +33 -19
  48. package/dist/commands/integrations.js +30 -18
  49. package/dist/commands/item-categories.js +30 -18
  50. package/dist/commands/items.js +30 -18
  51. package/dist/commands/journal-lines.js +30 -18
  52. package/dist/commands/journals.js +30 -18
  53. package/dist/commands/ledgers.js +30 -18
  54. package/dist/commands/office-locations.js +30 -18
  55. package/dist/commands/payment-method-ledger-mappings.js +30 -18
  56. package/dist/commands/payment-methods.js +30 -18
  57. package/dist/commands/payment-runs.js +30 -18
  58. package/dist/commands/payment-terms.js +30 -18
  59. package/dist/commands/price-lists.js +30 -18
  60. package/dist/commands/processor-mappings.js +30 -18
  61. package/dist/commands/processors.js +31 -19
  62. package/dist/commands/reconciliation.js +30 -18
  63. package/dist/commands/reports.js +30 -18
  64. package/dist/commands/salary-types.js +1018 -0
  65. package/dist/commands/sales-invoices.js +30 -18
  66. package/dist/commands/sales-orders.js +32 -19
  67. package/dist/commands/switch.d.ts +1 -0
  68. package/dist/commands/switch.js +58 -3
  69. package/dist/commands/tags.js +30 -18
  70. package/dist/commands/tax-codes.js +30 -18
  71. package/dist/commands/tax-matrix.js +30 -18
  72. package/dist/commands/tenants.js +30 -18
  73. package/dist/commands/text-templates.js +30 -18
  74. package/dist/commands/tools.js +30 -18
  75. package/dist/commands/vendor-invoices.js +33 -19
  76. package/dist/commands/webhooks.js +30 -18
  77. package/dist/commands/workflows.js +30 -18
  78. package/dist/index 2.js +1 -0
  79. package/dist/index.d 2.ts +1 -0
  80. package/dist/lib/auth.d.ts +16 -1
  81. package/dist/lib/auth.js +153 -45
  82. package/dist/lib/config.d.ts +7 -0
  83. package/dist/lib/config.js +6 -0
  84. package/oclif.manifest.json +2092 -669
  85. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Pubsub extends Command {
2
+ export default class AsyncEndpointsInternal extends Command {
3
3
  static description: string;
4
4
  static examples: never[];
5
5
  static args: {
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["webhook-events", "processor-jobs"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'webhook-events': { method: 'POST', path: '/v1/pubsub/webhook-events', summary: 'GCP Pub/Sub push endpoint for webhook event delivery', pathParams: [], hasBody: false, isMultipart: false },
12
- 'processor-jobs': { method: 'POST', path: '/v1/pubsub/processor-jobs', summary: 'GCP Pub/Sub push endpoint for processor-job.v1 envelopes (PDF rendering)', pathParams: [], hasBody: false, isMultipart: false },
11
+ 'webhook-events': { method: 'POST', path: '/v1/pubsub/webhook-events', summary: '[INTERNAL] Async push endpoint for webhook event delivery', pathParams: [], hasBody: false, isMultipart: false },
12
+ 'processor-jobs': { method: 'POST', path: '/v1/pubsub/processor-jobs', summary: '[INTERNAL] Async push endpoint for processor-job.v1 envelopes', pathParams: [], hasBody: false, isMultipart: false },
13
13
  };
14
- export default class Pubsub extends Command {
15
- static description = 'Manage pubsub';
14
+ export default class AsyncEndpointsInternal extends Command {
15
+ static description = 'Manage async endpoints internal';
16
16
  static examples = [];
17
17
  static args = {
18
18
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -38,7 +38,7 @@ export default class Pubsub extends Command {
38
38
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
39
39
  };
40
40
  async run() {
41
- const { args, flags } = await this.parse(Pubsub);
41
+ const { args, flags } = await this.parse(AsyncEndpointsInternal);
42
42
  // Handle import action specially
43
43
  if (args.action === 'import') {
44
44
  if (!HAS_IMPORT)
@@ -84,7 +84,7 @@ export default class Pubsub extends Command {
84
84
  // First path param always comes from positional argument
85
85
  value = args.id;
86
86
  if (!value)
87
- this.error('Missing required argument: id. Usage: aderis pubsub <action> <id>');
87
+ this.error('Missing required argument: id. Usage: aderis async-endpoints-internal <action> <id>');
88
88
  }
89
89
  else {
90
90
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -647,7 +647,7 @@ export default class Pubsub extends Command {
647
647
  }
648
648
  async runNestedImport(suffix, parentId, flags) {
649
649
  if (!parentId) {
650
- this.error(`Missing required argument: id. Usage: aderis pubsub import-${suffix} <parent-id> --file data.json`);
650
+ this.error(`Missing required argument: id. Usage: aderis async-endpoints-internal import-${suffix} <parent-id> --file data.json`);
651
651
  }
652
652
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
653
653
  if (!context)
@@ -809,32 +809,44 @@ export default class Pubsub extends Command {
809
809
  filterFields(data, fields) {
810
810
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
811
811
  const pick = (obj, keys) => {
812
- const result = {};
812
+ // Group keys by their first segment so sibling sub-paths
813
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
814
+ // and don't clobber each other on merge.
815
+ const directKeys = new Set();
816
+ const nestedKeys = new Map();
813
817
  for (const key of keys) {
814
818
  if (key.split('.').some(isUnsafeKey))
815
819
  continue;
816
820
  if (key.includes('.')) {
817
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
818
821
  const [first, ...rest] = key.split('.');
819
- const val = obj[first];
820
- if (val && typeof val === 'object') {
821
- const restKey = rest.join('.');
822
- if (Array.isArray(val)) {
823
- // Map over array elements (e.g., allocations[].salesInvoiceId)
824
- const existing = Array.isArray(result[first]) ? result[first] : [];
825
- result[first] = val.map((item, i) => ({
826
- ...(existing[i] || {}),
827
- ...pick(item, [restKey]),
828
- }));
829
- }
830
- else {
831
- const nested = pick(val, [restKey]);
832
- result[first] = { ...(result[first] || {}), ...nested };
833
- }
822
+ if (isUnsafeKey(first))
823
+ continue;
824
+ const list = nestedKeys.get(first);
825
+ if (list) {
826
+ list.push(rest.join('.'));
834
827
  }
828
+ else {
829
+ nestedKeys.set(first, [rest.join('.')]);
830
+ }
831
+ }
832
+ else {
833
+ directKeys.add(key);
835
834
  }
836
- else if (key in obj) {
835
+ }
836
+ const result = {};
837
+ for (const key of directKeys) {
838
+ if (key in obj)
837
839
  result[key] = obj[key];
840
+ }
841
+ for (const [first, restKeys] of nestedKeys) {
842
+ const val = obj[first];
843
+ if (!val || typeof val !== 'object')
844
+ continue;
845
+ if (Array.isArray(val)) {
846
+ result[first] = val.map(item => pick(item, restKeys));
847
+ }
848
+ else {
849
+ result[first] = pick(val, restKeys);
838
850
  }
839
851
  }
840
852
  return result;
@@ -0,0 +1,39 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AuditLog extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ };
28
+ run(): Promise<void>;
29
+ private runImport;
30
+ private runExport;
31
+ private runCamt053Import;
32
+ private runNestedImport;
33
+ private filterFields;
34
+ private sanitizeOutput;
35
+ private getColumns;
36
+ private getValue;
37
+ private printTable;
38
+ private printCsv;
39
+ }
@@ -3,18 +3,20 @@ import { apiRequest, ApiError } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = false;
6
- const HAS_EXPORT = false;
6
+ const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["get", "get-processingId"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "find-entity", "find-workflow"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'get': { method: 'GET', path: '/v1/processing-logs', summary: 'List recent inbox processing logs (newest first)', pathParams: [], hasBody: false, isMultipart: false },
12
- 'get-processingId': { method: 'GET', path: '/v1/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/audit-log', summary: 'Get audit log entries', pathParams: [], hasBody: false, isMultipart: false },
12
+ 'find-entity': { method: 'GET', path: '/v1/audit-log/entity/{entityId}', summary: 'Get audit history for a specific entity', pathParams: ['entityId'], hasBody: false, isMultipart: false },
13
+ 'find-workflow': { method: 'GET', path: '/v1/audit-log/workflow/{workflowId}', summary: 'Get audit log entries for a specific workflow', pathParams: ['workflowId'], hasBody: false, isMultipart: false },
13
14
  };
14
- export default class ProcessingLogs extends Command {
15
- static description = 'Manage processing logs';
15
+ export default class AuditLog extends Command {
16
+ static description = 'Manage audit log';
16
17
  static examples = [
17
- '<%= config.bin %> processing-logs get <id>',
18
+ '<%= config.bin %> audit-log list',
19
+ '<%= config.bin %> audit-log export --csv > audit-log.csv',
18
20
  ];
19
21
  static args = {
20
22
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -40,7 +42,7 @@ export default class ProcessingLogs extends Command {
40
42
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
41
43
  };
42
44
  async run() {
43
- const { args, flags } = await this.parse(ProcessingLogs);
45
+ const { args, flags } = await this.parse(AuditLog);
44
46
  // Handle import action specially
45
47
  if (args.action === 'import') {
46
48
  if (!HAS_IMPORT)
@@ -86,7 +88,7 @@ export default class ProcessingLogs extends Command {
86
88
  // First path param always comes from positional argument
87
89
  value = args.id;
88
90
  if (!value)
89
- this.error('Missing required argument: id. Usage: aderis processing-logs <action> <id>');
91
+ this.error('Missing required argument: id. Usage: aderis audit-log <action> <id>');
90
92
  }
91
93
  else {
92
94
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -649,7 +651,7 @@ export default class ProcessingLogs extends Command {
649
651
  }
650
652
  async runNestedImport(suffix, parentId, flags) {
651
653
  if (!parentId) {
652
- this.error(`Missing required argument: id. Usage: aderis processing-logs import-${suffix} <parent-id> --file data.json`);
654
+ this.error(`Missing required argument: id. Usage: aderis audit-log import-${suffix} <parent-id> --file data.json`);
653
655
  }
654
656
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
655
657
  if (!context)
@@ -811,32 +813,44 @@ export default class ProcessingLogs extends Command {
811
813
  filterFields(data, fields) {
812
814
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
813
815
  const pick = (obj, keys) => {
814
- const result = {};
816
+ // Group keys by their first segment so sibling sub-paths
817
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
818
+ // and don't clobber each other on merge.
819
+ const directKeys = new Set();
820
+ const nestedKeys = new Map();
815
821
  for (const key of keys) {
816
822
  if (key.split('.').some(isUnsafeKey))
817
823
  continue;
818
824
  if (key.includes('.')) {
819
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
820
825
  const [first, ...rest] = key.split('.');
821
- const val = obj[first];
822
- if (val && typeof val === 'object') {
823
- const restKey = rest.join('.');
824
- if (Array.isArray(val)) {
825
- // Map over array elements (e.g., allocations[].salesInvoiceId)
826
- const existing = Array.isArray(result[first]) ? result[first] : [];
827
- result[first] = val.map((item, i) => ({
828
- ...(existing[i] || {}),
829
- ...pick(item, [restKey]),
830
- }));
831
- }
832
- else {
833
- const nested = pick(val, [restKey]);
834
- result[first] = { ...(result[first] || {}), ...nested };
835
- }
826
+ if (isUnsafeKey(first))
827
+ continue;
828
+ const list = nestedKeys.get(first);
829
+ if (list) {
830
+ list.push(rest.join('.'));
836
831
  }
832
+ else {
833
+ nestedKeys.set(first, [rest.join('.')]);
834
+ }
835
+ }
836
+ else {
837
+ directKeys.add(key);
837
838
  }
838
- else if (key in obj) {
839
+ }
840
+ const result = {};
841
+ for (const key of directKeys) {
842
+ if (key in obj)
839
843
  result[key] = obj[key];
844
+ }
845
+ for (const [first, restKeys] of nestedKeys) {
846
+ const val = obj[first];
847
+ if (!val || typeof val !== 'object')
848
+ continue;
849
+ if (Array.isArray(val)) {
850
+ result[first] = val.map(item => pick(item, restKeys));
851
+ }
852
+ else {
853
+ result[first] = pick(val, restKeys);
840
854
  }
841
855
  }
842
856
  return result;
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AuthLogin extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'client-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ new: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ private createNewContext;
14
+ }
@@ -0,0 +1,116 @@
1
+ import { input, select } from '@inquirer/prompts';
2
+ import { Command, Flags } from '@oclif/core';
3
+ import ora from 'ora';
4
+ import { login, testAuth } from '../../lib/auth.js';
5
+ import { addContext, DEFAULT_CLIENT_IDS, getContext, getCurrentContext, } from '../../lib/config.js';
6
+ export default class AuthLogin extends Command {
7
+ static description = 'Login to an Aderis environment using OAuth';
8
+ static examples = [
9
+ '<%= config.bin %> auth login',
10
+ '<%= config.bin %> auth login --context my-client',
11
+ '<%= config.bin %> auth login --new --name my-client --environment dev',
12
+ ];
13
+ static flags = {
14
+ 'client-id': Flags.string({
15
+ description: 'OAuth client ID (uses default if not specified)',
16
+ }),
17
+ context: Flags.string({
18
+ char: 'c',
19
+ description: 'Context to login with (uses current context if not specified)',
20
+ }),
21
+ environment: Flags.string({
22
+ char: 'e',
23
+ description: 'Environment (dev, test, live)',
24
+ options: ['dev', 'test', 'live'],
25
+ }),
26
+ name: Flags.string({
27
+ description: 'Name for the new context (required with --new)',
28
+ }),
29
+ new: Flags.boolean({
30
+ char: 'n',
31
+ default: false,
32
+ description: 'Create a new context before logging in',
33
+ }),
34
+ };
35
+ async run() {
36
+ const { flags } = await this.parse(AuthLogin);
37
+ let context = null;
38
+ if (flags.new) {
39
+ context = await this.createNewContext(flags);
40
+ }
41
+ else if (flags.context) {
42
+ context = getContext(flags.context);
43
+ if (!context) {
44
+ this.error(`Context "${flags.context}" not found. Use --new to create it.`);
45
+ }
46
+ }
47
+ else {
48
+ context = getCurrentContext();
49
+ if (!context) {
50
+ this.log("No context found. Let's create one.");
51
+ context = await this.createNewContext(flags);
52
+ }
53
+ }
54
+ const spinner = ora(`Logging in to ${context.name} (${context.environment})...`).start();
55
+ try {
56
+ spinner.text = 'Waiting for browser authentication...';
57
+ await login(context);
58
+ spinner.text = 'Verifying authentication...';
59
+ const user = await testAuth(context.name);
60
+ spinner.succeed(`Successfully logged in as ${user.email || user.name || 'user'}`);
61
+ this.log(`\nContext: ${context.name}`);
62
+ this.log(`Environment: ${context.environment}`);
63
+ if (context.tenant_id) {
64
+ this.log(`Tenant ID: ${context.tenant_id}`);
65
+ }
66
+ }
67
+ catch (error) {
68
+ spinner.fail('Login failed');
69
+ if (error instanceof Error) {
70
+ this.error(error.message);
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+ async createNewContext(flags) {
76
+ // Get context name
77
+ let contextName = flags.name;
78
+ if (!contextName) {
79
+ contextName = await input({
80
+ message: 'Context name (e.g., acme-dev, my-company-prod):',
81
+ validate(value) {
82
+ if (!value.trim())
83
+ return 'Name is required';
84
+ if (!/^[\w-]+$/.test(value))
85
+ return 'Name can only contain letters, numbers, and hyphens';
86
+ if (getContext(value))
87
+ return `Context "${value}" already exists`;
88
+ return true;
89
+ },
90
+ });
91
+ }
92
+ // Get environment
93
+ let environment;
94
+ environment = flags.environment ? flags.environment : (await select({
95
+ choices: [
96
+ { description: 'Development environment', name: 'dev', value: 'dev' },
97
+ { description: 'Test/staging environment', name: 'test', value: 'test' },
98
+ { description: 'Production environment', name: 'live', value: 'live' },
99
+ ],
100
+ message: 'Environment:',
101
+ }));
102
+ const defaultClientId = DEFAULT_CLIENT_IDS[environment];
103
+ const clientId = flags['client-id'] || defaultClientId;
104
+ if (!clientId) {
105
+ this.error(`No default client ID configured for ${environment} environment. Please provide --client-id`);
106
+ }
107
+ const context = {
108
+ client_id: clientId,
109
+ environment,
110
+ name: contextName,
111
+ };
112
+ addContext(context);
113
+ this.log(`\nContext "${context.name}" created.`);
114
+ return context;
115
+ }
116
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AuthLogout extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,58 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { clearTokens, getContext, getCurrentContext } from '../../lib/config.js';
3
+ export default class AuthLogout extends Command {
4
+ static description = 'Logout from the current or specified context';
5
+ static examples = [
6
+ '<%= config.bin %> auth logout',
7
+ '<%= config.bin %> auth logout --context my-client',
8
+ '<%= config.bin %> auth logout --all',
9
+ ];
10
+ static flags = {
11
+ all: Flags.boolean({
12
+ char: 'a',
13
+ default: false,
14
+ description: 'Logout from all contexts',
15
+ }),
16
+ context: Flags.string({
17
+ char: 'c',
18
+ description: 'Specific context to logout from',
19
+ }),
20
+ };
21
+ async run() {
22
+ const { flags } = await this.parse(AuthLogout);
23
+ const { listContexts } = await import('../../lib/config.js');
24
+ if (flags.all) {
25
+ const contexts = listContexts();
26
+ for (const ctx of contexts) {
27
+ if (ctx.tokens) {
28
+ clearTokens(ctx.name);
29
+ this.log(`Logged out from ${ctx.name}`);
30
+ }
31
+ }
32
+ this.log('\nLogged out from all contexts.');
33
+ return;
34
+ }
35
+ let contextName;
36
+ if (flags.context) {
37
+ const ctx = getContext(flags.context);
38
+ if (!ctx) {
39
+ this.error(`Context "${flags.context}" not found.`);
40
+ }
41
+ contextName = flags.context;
42
+ }
43
+ else {
44
+ const current = getCurrentContext();
45
+ if (!current) {
46
+ this.error('No current context. Use --context to specify one.');
47
+ }
48
+ contextName = current.name;
49
+ }
50
+ const context = getContext(contextName);
51
+ if (!context?.tokens) {
52
+ this.log(`Already logged out from ${contextName}`);
53
+ return;
54
+ }
55
+ clearTokens(contextName);
56
+ this.log(`Successfully logged out from ${contextName}`);
57
+ }
58
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AuthStatus extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ private showAllContexts;
11
+ private showContextStatus;
12
+ }
@@ -0,0 +1,101 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { testAuth } from '../../lib/auth.js';
3
+ import { getContext, getCurrentContext, listContexts } from '../../lib/config.js';
4
+ export default class AuthStatus extends Command {
5
+ static description = 'Show authentication status for contexts';
6
+ static examples = [
7
+ '<%= config.bin %> auth status',
8
+ '<%= config.bin %> auth status --context my-client',
9
+ '<%= config.bin %> auth status --all',
10
+ ];
11
+ static flags = {
12
+ all: Flags.boolean({
13
+ char: 'a',
14
+ default: false,
15
+ description: 'Show status for all contexts',
16
+ }),
17
+ context: Flags.string({
18
+ char: 'c',
19
+ description: 'Specific context to check',
20
+ }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(AuthStatus);
24
+ if (flags.all) {
25
+ await this.showAllContexts();
26
+ return;
27
+ }
28
+ let contextName;
29
+ if (flags.context) {
30
+ const ctx = getContext(flags.context);
31
+ if (!ctx) {
32
+ this.error(`Context "${flags.context}" not found.`);
33
+ }
34
+ contextName = flags.context;
35
+ }
36
+ else {
37
+ const current = getCurrentContext();
38
+ if (!current) {
39
+ this.log('No current context configured.');
40
+ this.log('Run: aderis auth login --new');
41
+ return;
42
+ }
43
+ contextName = current.name;
44
+ }
45
+ await this.showContextStatus(contextName, true);
46
+ }
47
+ async showAllContexts() {
48
+ const contexts = listContexts();
49
+ const current = getCurrentContext();
50
+ if (contexts.length === 0) {
51
+ this.log('No contexts configured.');
52
+ this.log('Run: aderis auth login --new');
53
+ return;
54
+ }
55
+ this.log('Contexts:\n');
56
+ for (const ctx of contexts) {
57
+ const isCurrent = current?.name === ctx.name;
58
+ const prefix = isCurrent ? '* ' : ' ';
59
+ const status = ctx.tokens ? '✓ logged in' : '✗ not logged in';
60
+ this.log(`${prefix}${ctx.name} (${ctx.environment}) - ${status}`);
61
+ }
62
+ this.log('\n* = current context');
63
+ }
64
+ async showContextStatus(name, detailed) {
65
+ const context = getContext(name);
66
+ const current = getCurrentContext();
67
+ if (!context) {
68
+ this.error(`Context "${name}" not found.`);
69
+ }
70
+ const isCurrent = current?.name === name;
71
+ this.log(`Context: ${context.name}${isCurrent ? ' (current)' : ''}`);
72
+ this.log(`Environment: ${context.environment}`);
73
+ this.log(`Client ID: ${context.client_id}`);
74
+ if (!context.tokens) {
75
+ this.log(`Status: Not logged in`);
76
+ this.log(`\nRun: aderis auth login${isCurrent ? '' : ` --context ${name}`}`);
77
+ return;
78
+ }
79
+ this.log(`Tenant ID: ${context.tenant_id || 'N/A'}`);
80
+ if (context.tokens.expires_at) {
81
+ const expiresAt = new Date(context.tokens.expires_at);
82
+ const isExpired = expiresAt < new Date();
83
+ this.log(`Token expires: ${expiresAt.toLocaleString()}${isExpired ? ' (EXPIRED)' : ''}`);
84
+ }
85
+ if (detailed) {
86
+ try {
87
+ const user = await testAuth(name);
88
+ this.log(`\nLogged in as: ${user.email || user.name || 'Unknown'}`);
89
+ this.log('Status: ✓ Authenticated');
90
+ }
91
+ catch (error) {
92
+ if (error instanceof Error) {
93
+ this.log(`\nStatus: ✗ ${error.message}`);
94
+ }
95
+ else {
96
+ this.log('\nStatus: ✗ Authentication error');
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,19 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AuthTenant extends Command {
3
+ static args: {
4
+ action: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ tenantId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private interactiveSwitch;
15
+ private listTenants;
16
+ private showCurrentTenant;
17
+ private switchTenant;
18
+ private resolveTenantName;
19
+ }