@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.
- package/dist/commands/{processing-logs.d.ts → access-groups.d.ts} +1 -1
- package/dist/commands/{access-groups 2.js → access-groups.js} +30 -18
- package/dist/commands/account-transactions.d.ts +41 -0
- package/dist/commands/account-transactions.js +1032 -0
- package/dist/commands/accounts.d.ts +40 -0
- package/dist/commands/accounts.js +1024 -0
- package/dist/commands/agent-runs.d.ts +39 -0
- package/dist/commands/agent-runs.js +1013 -0
- package/dist/commands/{pubsub.d.ts → async-endpoints-internal.d.ts} +1 -1
- package/dist/commands/{pubsub.js → async-endpoints-internal.js} +37 -25
- package/dist/commands/audit-log.d.ts +39 -0
- package/dist/commands/{processing-logs.js → audit-log.js} +42 -28
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.js +116 -0
- package/dist/commands/auth/logout.d.ts +10 -0
- package/dist/commands/auth/logout.js +58 -0
- package/dist/commands/auth/status.d.ts +12 -0
- package/dist/commands/auth/status.js +101 -0
- package/dist/commands/auth/tenant.d.ts +19 -0
- package/dist/commands/auth/tenant.js +184 -0
- package/dist/commands/auth.d.ts +37 -0
- package/dist/commands/auth.js +871 -0
- package/dist/commands/bank-accounts.d.ts +41 -0
- package/dist/commands/bank-accounts.js +1032 -0
- package/dist/commands/bank-integrations.js +30 -18
- package/dist/commands/business-partners---contacts.js +30 -18
- package/dist/commands/business-partners.js +30 -18
- package/dist/commands/config/add.d.ts +13 -0
- package/dist/commands/config/add.js +120 -0
- package/dist/commands/config/current.d.ts +9 -0
- package/dist/commands/config/current.js +43 -0
- package/dist/commands/config/delete.d.ts +12 -0
- package/dist/commands/config/delete.js +67 -0
- package/dist/commands/config/list.d.ts +9 -0
- package/dist/commands/config/list.js +50 -0
- package/dist/commands/config/use.d.ts +9 -0
- package/dist/commands/config/use.js +40 -0
- package/dist/commands/credit-card-accounts.js +30 -18
- package/dist/commands/documents.js +30 -18
- package/dist/commands/dunning-configs.js +30 -18
- package/dist/commands/dunning-reminders.js +30 -18
- package/dist/commands/dunning-runs.js +30 -18
- package/dist/commands/employees.js +30 -18
- package/dist/commands/event-processor-mappings.js +30 -18
- package/dist/commands/files-internal.js +30 -18
- package/dist/commands/health.js +30 -18
- package/dist/commands/inbox.js +33 -19
- package/dist/commands/integrations.js +30 -18
- package/dist/commands/item-categories.js +30 -18
- package/dist/commands/items.js +30 -18
- package/dist/commands/journal-lines.js +30 -18
- package/dist/commands/journals.js +30 -18
- package/dist/commands/ledgers.js +30 -18
- package/dist/commands/office-locations.js +30 -18
- package/dist/commands/payment-method-ledger-mappings.js +30 -18
- package/dist/commands/payment-methods.js +30 -18
- package/dist/commands/payment-runs.js +30 -18
- package/dist/commands/payment-terms.js +30 -18
- package/dist/commands/price-lists.js +30 -18
- package/dist/commands/processor-mappings.js +30 -18
- package/dist/commands/processors.js +31 -19
- package/dist/commands/reconciliation.js +30 -18
- package/dist/commands/reports.js +30 -18
- package/dist/commands/salary-types.js +1018 -0
- package/dist/commands/sales-invoices.js +30 -18
- package/dist/commands/sales-orders.js +32 -19
- package/dist/commands/switch.d.ts +1 -0
- package/dist/commands/switch.js +58 -3
- package/dist/commands/tags.js +30 -18
- package/dist/commands/tax-codes.js +30 -18
- package/dist/commands/tax-matrix.js +30 -18
- package/dist/commands/tenants.js +30 -18
- package/dist/commands/text-templates.js +30 -18
- package/dist/commands/tools.js +30 -18
- package/dist/commands/vendor-invoices.js +33 -19
- package/dist/commands/webhooks.js +30 -18
- package/dist/commands/workflows.js +30 -18
- package/dist/index 2.js +1 -0
- package/dist/index.d 2.ts +1 -0
- package/dist/lib/auth.d.ts +16 -1
- package/dist/lib/auth.js +153 -45
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +6 -0
- package/oclif.manifest.json +2092 -669
- package/package.json +2 -2
|
@@ -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: '
|
|
12
|
-
'processor-jobs': { method: 'POST', path: '/v1/pubsub/processor-jobs', summary: '
|
|
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
|
|
15
|
-
static description = 'Manage
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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 =
|
|
6
|
+
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [...["
|
|
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
|
-
'
|
|
12
|
-
'
|
|
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
|
|
15
|
-
static description = 'Manage
|
|
15
|
+
export default class AuditLog extends Command {
|
|
16
|
+
static description = 'Manage audit log';
|
|
16
17
|
static examples = [
|
|
17
|
-
'<%= config.bin %>
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
-
|
|
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
|
+
}
|