@aderis-erp/cli 1.0.0 → 1.0.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/{access-groups.js → access-groups 2.js } +6 -4
  3. package/dist/commands/bank-integrations.d.ts +2 -0
  4. package/dist/commands/bank-integrations.js +11 -1
  5. package/dist/commands/documents.js +6 -4
  6. package/dist/commands/inbox.js +1 -1
  7. package/dist/commands/processing-logs.js +3 -2
  8. package/dist/commands/{audit-log.d.ts → pubsub.d.ts} +2 -2
  9. package/dist/commands/{audit-log.js → pubsub.js} +10 -14
  10. package/dist/commands/sales-invoices.js +2 -1
  11. package/dist/commands/sales-orders.js +2 -1
  12. package/dist/lib/auth.js +16 -7
  13. package/oclif.manifest.json +660 -2403
  14. package/package.json +5 -4
  15. package/dist/commands/access-groups.d.ts +0 -39
  16. package/dist/commands/account-statement-runs.d.ts +0 -40
  17. package/dist/commands/account-statement-runs.js +0 -1004
  18. package/dist/commands/account-transactions.d.ts +0 -41
  19. package/dist/commands/account-transactions.js +0 -1020
  20. package/dist/commands/accounts.d.ts +0 -40
  21. package/dist/commands/accounts.js +0 -1012
  22. package/dist/commands/agent-runs.d.ts +0 -39
  23. package/dist/commands/agent-runs.js +0 -1001
  24. package/dist/commands/auth/login.d.ts +0 -14
  25. package/dist/commands/auth/login.js +0 -116
  26. package/dist/commands/auth/logout.d.ts +0 -10
  27. package/dist/commands/auth/logout.js +0 -58
  28. package/dist/commands/auth/status.d.ts +0 -12
  29. package/dist/commands/auth/status.js +0 -101
  30. package/dist/commands/auth/tenant.d.ts +0 -18
  31. package/dist/commands/auth/tenant.js +0 -168
  32. package/dist/commands/auth.d.ts +0 -37
  33. package/dist/commands/auth.js +0 -871
  34. package/dist/commands/bank-accounts.d.ts +0 -41
  35. package/dist/commands/bank-accounts.js +0 -1020
  36. package/dist/commands/bank-statements.d.ts +0 -40
  37. package/dist/commands/bank-statements.js +0 -998
  38. package/dist/commands/config/add.d.ts +0 -13
  39. package/dist/commands/config/add.js +0 -120
  40. package/dist/commands/config/current.d.ts +0 -9
  41. package/dist/commands/config/current.js +0 -43
  42. package/dist/commands/config/delete.d.ts +0 -12
  43. package/dist/commands/config/delete.js +0 -67
  44. package/dist/commands/config/list.d.ts +0 -9
  45. package/dist/commands/config/list.js +0 -50
  46. package/dist/commands/config/use.d.ts +0 -9
  47. package/dist/commands/config/use.js +0 -40
  48. package/dist/commands/salary-types.js +0 -1006
package/README.md CHANGED
@@ -14,7 +14,7 @@ Command-line interface for Aderis Finance ERP. Manage your Aderis account, execu
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- npm install -g aderis-cli
17
+ npm install -g @aderis-erp/cli
18
18
  aderis --help
19
19
  ```
20
20
 
@@ -3,24 +3,26 @@ 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 = true;
6
- const HAS_EXPORT = false;
6
+ const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["get", "create", "get-all", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'get': { method: 'GET', path: '/v1/access-groups', summary: 'List access groups (paginated)', pathParams: [], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/access-groups', summary: 'List access groups (paginated)', pathParams: [], hasBody: false, isMultipart: false },
12
12
  'create': { method: 'POST', path: '/v1/access-groups', summary: 'Create a new access group', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get-all': { method: 'GET', path: '/v1/access-groups/{id}', summary: 'Get a single access group by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
13
+ 'get': { method: 'GET', path: '/v1/access-groups/{id}', summary: 'Get a single access group by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
14
14
  'update': { method: 'PATCH', path: '/v1/access-groups/{id}', summary: 'Update an existing access group', pathParams: ['id'], hasBody: true, isMultipart: false },
15
15
  'delete': { method: 'DELETE', path: '/v1/access-groups/{id}', summary: 'Delete an access group', pathParams: ['id'], hasBody: false, isMultipart: false },
16
16
  };
17
17
  export default class AccessGroups extends Command {
18
18
  static description = 'Manage access groups';
19
19
  static examples = [
20
+ '<%= config.bin %> access-groups list',
20
21
  '<%= config.bin %> access-groups get <id>',
21
22
  '<%= config.bin %> access-groups create --data \'{...}\'',
22
23
  '<%= config.bin %> access-groups update <id> --set field=value',
23
24
  '<%= config.bin %> access-groups import --file data.json',
25
+ '<%= config.bin %> access-groups export --csv > access-groups.csv',
24
26
  ];
25
27
  static args = {
26
28
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -14,6 +14,8 @@ export default class BankIntegrations extends Command {
14
14
  data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'import-run-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'statement-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
19
  page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
20
  limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
21
  include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -6,7 +6,7 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["initiate", "providers-supported", "providers-consent-status", "delete-providers-consent", "list", "create", "get", "update", "delete", "log"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["initiate", "providers-supported", "providers-consent-status", "delete-providers-consent", "list", "create", "get", "update", "delete", "log", "list-account-statement-runs", "trigger-account-statement-runs", "get-account-statement-runs", "account-statement-runs-files", "trigger-account-statement-runs-reimport", "list-statements", "statements", "download-statements-xml"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'initiate': { method: 'POST', path: '/v1/bank-integrations/initiate', summary: 'Initiate the OAuth/auth flow for a bank integration', pathParams: [], hasBody: true, isMultipart: false },
12
12
  'providers-supported': { method: 'GET', path: '/v1/bank-integrations/providers/supported', summary: 'List supported bLink providers', pathParams: [], hasBody: false, isMultipart: false },
@@ -18,6 +18,14 @@ const ENDPOINTS = {
18
18
  'update': { method: 'PATCH', path: '/v1/bank-integrations/{id}', summary: 'Update the display name of a BankIntegration', pathParams: ['id'], hasBody: true, isMultipart: false },
19
19
  'delete': { method: 'DELETE', path: '/v1/bank-integrations/{id}', summary: 'Delete a BankIntegration', pathParams: ['id'], hasBody: false, isMultipart: false },
20
20
  'log': { method: 'GET', path: '/v1/bank-integrations/{id}/log', summary: 'Get activity log for a bank integration', pathParams: ['id'], hasBody: false, isMultipart: false },
21
+ 'list-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'List bank statement import runs', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false },
22
+ 'trigger-account-statement-runs': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs', summary: 'Trigger a bank statement import', pathParams: ['bankIntegrationId'], hasBody: true, isMultipart: false },
23
+ 'get-account-statement-runs': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}', summary: 'Get import run details', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false },
24
+ 'account-statement-runs-files': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/files', summary: 'Get files for an import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: false, isMultipart: false },
25
+ 'trigger-account-statement-runs-reimport': { method: 'POST', path: '/v1/bank-integrations/{bankIntegrationId}/account-statement-runs/{importRunId}/reimport', summary: 'Trigger reimport of an existing account statement import run', pathParams: ['bankIntegrationId', 'importRunId'], hasBody: true, isMultipart: false },
26
+ 'list-statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements', summary: 'List CAMT.053 statements for a bank integration', pathParams: ['bankIntegrationId'], hasBody: false, isMultipart: false },
27
+ 'statements': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}', summary: 'Get CAMT.053 statement detail', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false },
28
+ 'download-statements-xml': { method: 'GET', path: '/v1/bank-integrations/{bankIntegrationId}/statements/{statementId}/xml', summary: 'Get raw CAMT.053 XML for a statement', pathParams: ['bankIntegrationId', 'statementId'], hasBody: false, isMultipart: false },
21
29
  };
22
30
  export default class BankIntegrations extends Command {
23
31
  static description = 'Manage bank integrations';
@@ -41,6 +49,8 @@ export default class BankIntegrations extends Command {
41
49
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
42
50
  file: Flags.string({ char: 'f', description: 'Read request body from file' }),
43
51
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
52
+ 'import-run-id': Flags.string({ description: 'Path param: importRunId' }),
53
+ 'statement-id': Flags.string({ description: 'Path param: statementId' }),
44
54
  page: Flags.string({ description: 'Page number' }),
45
55
  limit: Flags.string({ description: 'Items per page' }),
46
56
  include: Flags.string({ description: 'Relations to include' }),
@@ -3,14 +3,14 @@ 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 = true;
6
- const HAS_EXPORT = false;
6
+ const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["get", "create", "get-all", "update", "delete", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'get': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false },
12
12
  'create': { method: 'POST', path: '/v1/documents', summary: 'Create a new document (letterbox)', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get-all': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false },
13
+ 'get': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false },
14
14
  'update': { method: 'PATCH', path: '/v1/documents/{id}', summary: 'Patch an existing document', pathParams: ['id'], hasBody: true, isMultipart: false },
15
15
  'delete': { method: 'DELETE', path: '/v1/documents/{id}', summary: 'Soft-delete (discard) a document', pathParams: ['id'], hasBody: true, isMultipart: false },
16
16
  'reclassify': { method: 'POST', path: '/v1/documents/{id}/reclassify', summary: 'Reclassify a document to a new type', pathParams: ['id'], hasBody: true, isMultipart: false },
@@ -18,10 +18,12 @@ const ENDPOINTS = {
18
18
  export default class Documents extends Command {
19
19
  static description = 'Manage documents';
20
20
  static examples = [
21
+ '<%= config.bin %> documents list',
21
22
  '<%= config.bin %> documents get <id>',
22
23
  '<%= config.bin %> documents create --data \'{...}\'',
23
24
  '<%= config.bin %> documents update <id> --set field=value',
24
25
  '<%= config.bin %> documents import --file data.json',
26
+ '<%= config.bin %> documents export --csv > documents.csv',
25
27
  ];
26
28
  static args = {
27
29
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
@@ -8,7 +8,7 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["upload", "email-webhook", "document-types"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: false },
11
+ 'upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: true },
12
12
  'email-webhook': { method: 'POST', path: '/v1/inbox/email-webhook/{secret}', summary: 'Email webhook for document ingestion (SendGrid inbound-parse)', pathParams: ['secret'], hasBody: false, isMultipart: false },
13
13
  'document-types': { method: 'GET', path: '/v1/inbox/document-types', summary: 'Get available document types and per-parent allowed types', pathParams: [], hasBody: false, isMultipart: false },
14
14
  };
@@ -6,9 +6,10 @@ const HAS_IMPORT = false;
6
6
  const HAS_EXPORT = false;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["get"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["get", "get-processingId"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'get': { method: 'GET', path: '/v1/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false },
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 },
12
13
  };
13
14
  export default class ProcessingLogs extends Command {
14
15
  static description = 'Manage processing logs';
@@ -1,7 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class AuditLog extends Command {
2
+ export default class Pubsub extends Command {
3
3
  static description: string;
4
- static examples: string[];
4
+ static examples: never[];
5
5
  static args: {
6
6
  action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
7
  id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -3,21 +3,17 @@ 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 = true;
6
+ const HAS_EXPORT = false;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
- const SUBCOMMANDS = [...["list", "find-entity", "find-workflow"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["webhook-events", "processor-jobs"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
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 },
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 },
14
13
  };
15
- export default class AuditLog extends Command {
16
- static description = 'Manage audit log';
17
- static examples = [
18
- '<%= config.bin %> audit-log list',
19
- '<%= config.bin %> audit-log export --csv > audit-log.csv',
20
- ];
14
+ export default class Pubsub extends Command {
15
+ static description = 'Manage pubsub';
16
+ static examples = [];
21
17
  static args = {
22
18
  action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
23
19
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
@@ -42,7 +38,7 @@ export default class AuditLog extends Command {
42
38
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
43
39
  };
44
40
  async run() {
45
- const { args, flags } = await this.parse(AuditLog);
41
+ const { args, flags } = await this.parse(Pubsub);
46
42
  // Handle import action specially
47
43
  if (args.action === 'import') {
48
44
  if (!HAS_IMPORT)
@@ -88,7 +84,7 @@ export default class AuditLog extends Command {
88
84
  // First path param always comes from positional argument
89
85
  value = args.id;
90
86
  if (!value)
91
- this.error('Missing required argument: id. Usage: aderis audit-log <action> <id>');
87
+ this.error('Missing required argument: id. Usage: aderis pubsub <action> <id>');
92
88
  }
93
89
  else {
94
90
  const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
@@ -651,7 +647,7 @@ export default class AuditLog extends Command {
651
647
  }
652
648
  async runNestedImport(suffix, parentId, flags) {
653
649
  if (!parentId) {
654
- this.error(`Missing required argument: id. Usage: aderis audit-log import-${suffix} <parent-id> --file data.json`);
650
+ this.error(`Missing required argument: id. Usage: aderis pubsub import-${suffix} <parent-id> --file data.json`);
655
651
  }
656
652
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
657
653
  if (!context)
@@ -6,7 +6,7 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "create-migrated-invoice", "pdf", "get", "update", "delete", "approve", "validate", "request-approval", "send", "send-resend", "cancel", "copy", "replace", "create-line-items", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "create-migrated-invoice", "pdf", "get", "update", "delete", "approve", "validate", "request-approval", "send", "send-resend", "cancel", "copy", "replace", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/sales-invoices', summary: 'Get all sales invoices with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
12
  'create': { method: 'POST', path: '/v1/sales-invoices', summary: 'Create a new sales invoice', pathParams: [], hasBody: true, isMultipart: false },
@@ -24,6 +24,7 @@ const ENDPOINTS = {
24
24
  'copy': { method: 'POST', path: '/v1/sales-invoices/{id}/copy', summary: 'Create a copy of a cancelled invoice', pathParams: ['id'], hasBody: false, isMultipart: false },
25
25
  'replace': { method: 'POST', path: '/v1/sales-invoices/{id}/replace', summary: 'Replace invoice with corrected version (creates new CORRECTED_INVOICE)', pathParams: ['id'], hasBody: false, isMultipart: false },
26
26
  'create-line-items': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items', summary: 'Add a line item to a sales invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false },
27
+ 'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-invoices/{invoiceId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['invoiceId'], hasBody: true, isMultipart: false },
27
28
  'update-line-items': { method: 'PATCH', path: '/v1/sales-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a sales invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false },
28
29
  'delete-line-items': { method: 'DELETE', path: '/v1/sales-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: false, isMultipart: false },
29
30
  'list-notes': { method: 'GET', path: '/v1/sales-invoices/{salesInvoiceId}/notes', summary: 'Get all notes for a sales invoice', pathParams: ['salesInvoiceId'], hasBody: false, isMultipart: false },
@@ -6,7 +6,7 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/sales-orders', summary: 'Get all sales orders with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
12
  'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false },
@@ -21,6 +21,7 @@ const ENDPOINTS = {
21
21
  'replace': { method: 'POST', path: '/v1/sales-orders/{id}/replace', summary: 'Replace order with corrected version (APPROVED → creates new DRAFT)', pathParams: ['id'], hasBody: false, isMultipart: false },
22
22
  'create-invoice': { method: 'POST', path: '/v1/sales-orders/{id}/create-invoice', summary: 'Create invoice from sales order (ACCEPTED → creates DRAFT invoice)', pathParams: ['id'], hasBody: false, isMultipart: false },
23
23
  'create-line-items': { method: 'POST', path: '/v1/sales-orders/{orderId}/line-items', summary: 'Add a line item to a sales order', pathParams: ['orderId'], hasBody: true, isMultipart: false },
24
+ 'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['orderId'], hasBody: true, isMultipart: false },
24
25
  'update-line-items': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false },
25
26
  'delete-line-items': { method: 'DELETE', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false },
26
27
  'list-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Get all notes for a sales order', pathParams: ['salesOrderId'], hasBody: false, isMultipart: false },
package/dist/lib/auth.js CHANGED
@@ -5,7 +5,7 @@ import * as http from 'node:http';
5
5
  import { URL, URLSearchParams } from 'node:url';
6
6
  import open from 'open';
7
7
  import { getBaseUrls, getContext, saveTokens } from './config.js';
8
- function buildValidatedUrl(baseUrl) {
8
+ function buildValidatedUrl(baseUrl, path) {
9
9
  try {
10
10
  // Minimal path validation
11
11
  if (baseUrl.includes('/../') || /\/%2e%2e\//i.test(baseUrl)) {
@@ -24,6 +24,15 @@ function buildValidatedUrl(baseUrl) {
24
24
  if (!['http:', 'https:'].includes(url.protocol)) {
25
25
  throw new Error('Invalid protocol');
26
26
  }
27
+ // Add path if provided
28
+ if (path) {
29
+ if (path.includes('/../') || /\/%2e%2e\//i.test(path)) {
30
+ throw new Error('Invalid path');
31
+ }
32
+ // Ensure path starts with / and append to pathname
33
+ const cleanPath = path.startsWith('/') ? path : '/' + path;
34
+ url.pathname = url.pathname.replace(/\/$/, '') + cleanPath;
35
+ }
27
36
  return url.href;
28
37
  }
29
38
  catch {
@@ -234,7 +243,7 @@ async function exchangeCodeForTokens(context, code, codeVerifier) {
234
243
  if (context.client_secret) {
235
244
  params.set('client_secret', context.client_secret);
236
245
  }
237
- const response = await axios.post(buildValidatedUrl(`${authUrl}/oauth/token`), params.toString(), {
246
+ const response = await axios.post(buildValidatedUrl(authUrl, '/oauth/token'), params.toString(), {
238
247
  headers: {
239
248
  Accept: 'application/json',
240
249
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -267,7 +276,7 @@ export async function refreshTokens(contextName) {
267
276
  }
268
277
  let response;
269
278
  try {
270
- response = await axios.post(buildValidatedUrl(`${authUrl}/oauth/token`), params.toString(), {
279
+ response = await axios.post(buildValidatedUrl(authUrl, '/oauth/token'), params.toString(), {
271
280
  headers: {
272
281
  Accept: 'application/json',
273
282
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -316,7 +325,7 @@ export async function testAuth(contextName) {
316
325
  }
317
326
  const token = await getValidToken(contextName);
318
327
  const { api: apiUrl } = getBaseUrls(context.environment);
319
- const response = await axios.get(buildValidatedUrl(`${apiUrl}/v1/auth/me`), {
328
+ const response = await axios.get(buildValidatedUrl(apiUrl, '/v1/auth/me'), {
320
329
  headers: {
321
330
  Accept: 'application/json',
322
331
  Authorization: `Bearer ${token}`,
@@ -334,7 +343,7 @@ export async function listTenants(contextName) {
334
343
  let response;
335
344
  try {
336
345
  // Frontegg endpoint to list user's tenants
337
- response = await axios.get(buildValidatedUrl(`${authUrl}/identity/resources/users/v2/me/tenants`), {
346
+ response = await axios.get(buildValidatedUrl(authUrl, '/identity/resources/users/v2/me/tenants'), {
338
347
  headers: {
339
348
  Accept: 'application/json',
340
349
  Authorization: `Bearer ${token}`,
@@ -393,7 +402,7 @@ export async function switchTenant(contextName, tenantId) {
393
402
  const token = await getValidToken(contextName);
394
403
  try {
395
404
  // Step 1: PUT to switch the user's active tenant
396
- await axios.put(buildValidatedUrl(`${authUrl}/identity/resources/users/v1/tenant`), { tenantId }, {
405
+ await axios.put(buildValidatedUrl(authUrl, '/identity/resources/users/v1/tenant'), { tenantId }, {
397
406
  headers: {
398
407
  Accept: 'application/json',
399
408
  Authorization: `Bearer ${token}`,
@@ -401,7 +410,7 @@ export async function switchTenant(contextName, tenantId) {
401
410
  },
402
411
  });
403
412
  // Step 2: Refresh tokens to get new tokens for the switched tenant
404
- const response = await axios.post(buildValidatedUrl(`${authUrl}/oauth/token`), new URLSearchParams({
413
+ const response = await axios.post(buildValidatedUrl(authUrl, '/oauth/token'), new URLSearchParams({
405
414
  client_id: context.client_id,
406
415
  grant_type: 'refresh_token',
407
416
  refresh_token: context.tokens.refresh_token,