@aderis-erp/cli 1.0.13 → 1.0.15

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 (86) hide show
  1. package/dist/commands/access-groups.js +21 -21
  2. package/dist/commands/account-transaction-allocations.js +21 -21
  3. package/dist/commands/account-transactions.js +23 -23
  4. package/dist/commands/accounts.js +23 -23
  5. package/dist/commands/agent-platform.js +23 -22
  6. package/dist/commands/analytics.d.ts +40 -0
  7. package/dist/commands/{documents.js → analytics.js} +33 -43
  8. package/dist/commands/audit-log.js +24 -24
  9. package/dist/commands/auth/login.js +11 -2
  10. package/dist/commands/auth/logout.d.ts +2 -0
  11. package/dist/commands/auth/logout.js +19 -6
  12. package/dist/commands/auth/status.js +26 -12
  13. package/dist/commands/auth/tenant.js +12 -3
  14. package/dist/commands/auth.js +32 -17
  15. package/dist/commands/bank-accounts.js +21 -21
  16. package/dist/commands/bank-integrations.js +21 -21
  17. package/dist/commands/business-partners.js +29 -26
  18. package/dist/commands/chat.js +21 -21
  19. package/dist/commands/config/add.d.ts +9 -0
  20. package/dist/commands/config/add.js +64 -2
  21. package/dist/commands/config/current.js +9 -4
  22. package/dist/commands/config/list.js +9 -4
  23. package/dist/commands/config/set-api-key.d.ts +10 -0
  24. package/dist/commands/config/set-api-key.js +49 -0
  25. package/dist/commands/config/use.js +3 -4
  26. package/dist/commands/credit-card-accounts.js +21 -21
  27. package/dist/commands/delivery-notes.js +28 -25
  28. package/dist/commands/dunning-configs.js +21 -21
  29. package/dist/commands/dunning-reminders.js +21 -21
  30. package/dist/commands/dunning-runs.js +21 -21
  31. package/dist/commands/email-transactions.js +21 -21
  32. package/dist/commands/employees.js +23 -23
  33. package/dist/commands/entity-configurations.js +21 -21
  34. package/dist/commands/event-processor-mappings.js +21 -21
  35. package/dist/commands/{documents.d.ts → files.d.ts} +1 -1
  36. package/dist/commands/{files-internal.js → files.js} +39 -33
  37. package/dist/commands/health.js +21 -21
  38. package/dist/commands/inbox.d.ts +1 -0
  39. package/dist/commands/inbox.js +25 -23
  40. package/dist/commands/integrations.js +21 -21
  41. package/dist/commands/item-categories.js +21 -21
  42. package/dist/commands/items.js +23 -23
  43. package/dist/commands/journal-lines.js +21 -21
  44. package/dist/commands/journals.js +27 -24
  45. package/dist/commands/ledgers.js +25 -22
  46. package/dist/commands/office-locations.js +21 -21
  47. package/dist/commands/payment-methods.js +21 -21
  48. package/dist/commands/payment-runs.js +23 -22
  49. package/dist/commands/payment-terms.js +21 -21
  50. package/dist/commands/payments.js +21 -21
  51. package/dist/commands/price-lists.js +21 -21
  52. package/dist/commands/processor-mappings.js +21 -21
  53. package/dist/commands/processors.js +21 -21
  54. package/dist/commands/purchase-order-types.js +21 -21
  55. package/dist/commands/purchase-orders.d.ts +1 -0
  56. package/dist/commands/purchase-orders.js +32 -25
  57. package/dist/commands/reports.js +21 -21
  58. package/dist/commands/salary-types.js +21 -21
  59. package/dist/commands/sales-invoices.js +33 -31
  60. package/dist/commands/sales-orders.js +23 -23
  61. package/dist/commands/saved-views.js +21 -21
  62. package/dist/commands/scheduled-processor-mappings.d.ts +40 -0
  63. package/dist/commands/scheduled-processor-mappings.js +1178 -0
  64. package/dist/commands/schedules.js +21 -21
  65. package/dist/commands/switch.js +10 -3
  66. package/dist/commands/{files-internal.d.ts → tag-configurations.d.ts} +1 -1
  67. package/dist/commands/tag-configurations.js +1178 -0
  68. package/dist/commands/tags.js +21 -21
  69. package/dist/commands/tax-codes.js +21 -21
  70. package/dist/commands/tax-matrix.js +21 -21
  71. package/dist/commands/tenants.js +23 -23
  72. package/dist/commands/text-templates.js +21 -21
  73. package/dist/commands/tools.js +21 -21
  74. package/dist/commands/vat-declarations.js +21 -21
  75. package/dist/commands/vendor-invoices.js +25 -25
  76. package/dist/commands/webhooks.js +21 -21
  77. package/dist/commands/workflows.js +21 -21
  78. package/dist/lib/api-client.d.ts +8 -0
  79. package/dist/lib/api-client.js +31 -6
  80. package/dist/lib/auth.d.ts +8 -0
  81. package/dist/lib/auth.js +51 -7
  82. package/dist/lib/base-api-command.js +6 -4
  83. package/dist/lib/config.d.ts +31 -1
  84. package/dist/lib/config.js +31 -0
  85. package/oclif.manifest.json +1099 -443
  86. package/package.json +2 -2
@@ -1,14 +1,22 @@
1
- import { confirm, input, select } from '@inquirer/prompts';
1
+ import { confirm, input, password, select } from '@inquirer/prompts';
2
2
  import { Command, Flags } from '@oclif/core';
3
- import { addContext, DEFAULT_CLIENT_IDS, getContext } from '../../lib/config.js';
3
+ import { addContext, DEFAULT_CLIENT_IDS, getContext, } from '../../lib/config.js';
4
4
  export default class ConfigAdd extends Command {
5
5
  static description = 'Add a new context configuration';
6
6
  static examples = [
7
7
  '<%= config.bin %> config add',
8
8
  '<%= config.bin %> config add --name my-client --environment live',
9
9
  '<%= config.bin %> config add --name dev-context --environment dev --client-id abc123',
10
+ '<%= config.bin %> config add --name ci --environment live --auth-type api-key --api-key <key>',
10
11
  ];
11
12
  static flags = {
13
+ 'api-key': Flags.string({
14
+ description: 'API key to store (only with --auth-type api-key). Prompted for if omitted.',
15
+ }),
16
+ 'auth-type': Flags.string({
17
+ description: 'How to authenticate: oauth2 (browser login) or api-key (x-api-key header)',
18
+ options: ['oauth2', 'api-key'],
19
+ }),
12
20
  'client-id': Flags.string({
13
21
  description: 'OAuth client ID (uses default if not specified)',
14
22
  }),
@@ -71,6 +79,35 @@ export default class ConfigAdd extends Command {
71
79
  message: 'Environment:',
72
80
  }));
73
81
  }
82
+ // Get auth type. The flag uses the hyphenated `api-key` spelling; the stored
83
+ // value is the `api_key` AuthType. Only prompt when nothing in the invocation
84
+ // implies an answer, so existing non-interactive usage keeps working.
85
+ const authType = await this.resolveAuthType(flags);
86
+ if (authType === 'api_key') {
87
+ if (flags['client-id'] || flags['client-secret']) {
88
+ this.error('--client-id and --client-secret only apply to OAuth contexts; omit them with --auth-type api-key.');
89
+ }
90
+ const apiKey = flags['api-key'] ??
91
+ (await password({
92
+ mask: true,
93
+ message: 'API key:',
94
+ validate: (value) => (value.trim() ? true : 'API key is required'),
95
+ }));
96
+ if (!apiKey.trim()) {
97
+ this.error('API key is required');
98
+ }
99
+ addContext({
100
+ api_key: apiKey.trim(),
101
+ auth_type: 'api_key',
102
+ environment,
103
+ name,
104
+ });
105
+ this.log(`\nContext "${name}" created successfully.`);
106
+ this.log(`Environment: ${environment}`);
107
+ this.log(`Auth: API key (sent as x-api-key)`);
108
+ this.log(`\nNo login needed — the CLI is ready to use.`);
109
+ return;
110
+ }
74
111
  // Get client ID
75
112
  let clientId = flags['client-id'];
76
113
  if (!clientId) {
@@ -107,6 +144,7 @@ export default class ConfigAdd extends Command {
107
144
  }
108
145
  // Create the context
109
146
  addContext({
147
+ auth_type: 'oauth2',
110
148
  client_id: clientId,
111
149
  client_secret: clientSecret,
112
150
  environment,
@@ -117,4 +155,28 @@ export default class ConfigAdd extends Command {
117
155
  this.log(`Client ID: ${clientId}`);
118
156
  this.log(`\nTo authenticate, run: aderis auth login`);
119
157
  }
158
+ /**
159
+ * Decide the auth type from flags, falling back to a prompt only when the
160
+ * invocation leaves it genuinely open. `--client-id`/`--client-secret` imply
161
+ * OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
162
+ * scripted `config add` calls working exactly as before API-key support.
163
+ */
164
+ async resolveAuthType(flags) {
165
+ if (flags['auth-type']) {
166
+ return flags['auth-type'] === 'api-key' ? 'api_key' : 'oauth2';
167
+ }
168
+ // A key without --auth-type unambiguously means API-key auth.
169
+ if (flags['api-key'])
170
+ return 'api_key';
171
+ // OAuth-only flags, or a non-interactive shell: keep the historical default.
172
+ if (flags['client-id'] || flags['client-secret'] || !process.stdin.isTTY)
173
+ return 'oauth2';
174
+ return select({
175
+ choices: [
176
+ { description: 'Browser login via OAuth2 (PKCE)', name: 'oauth2', value: 'oauth2' },
177
+ { description: 'Long-lived API key, sent as the x-api-key header', name: 'api-key', value: 'api_key' },
178
+ ],
179
+ message: 'Authentication method:',
180
+ });
181
+ }
120
182
  }
@@ -1,5 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
- import { getCurrentContext } from '../../lib/config.js';
2
+ import { getAuthType, getCurrentContext, hasCredentials } from '../../lib/config.js';
3
3
  export default class ConfigCurrent extends Command {
4
4
  static description = 'Show the current context';
5
5
  static examples = ['<%= config.bin %> config current', '<%= config.bin %> config current --json'];
@@ -24,7 +24,8 @@ export default class ConfigCurrent extends Command {
24
24
  }
25
25
  if (flags.json) {
26
26
  this.log(JSON.stringify({
27
- authenticated: Boolean(context.tokens),
27
+ auth_type: getAuthType(context),
28
+ authenticated: hasCredentials(context),
28
29
  client_id: context.client_id,
29
30
  environment: context.environment,
30
31
  name: context.name,
@@ -32,12 +33,16 @@ export default class ConfigCurrent extends Command {
32
33
  }, null, 2));
33
34
  return;
34
35
  }
36
+ const authType = getAuthType(context);
35
37
  this.log(`Current context: ${context.name}`);
36
38
  this.log(`Environment: ${context.environment}`);
37
- this.log(`Client ID: ${context.client_id}`);
39
+ this.log(`Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
40
+ if (authType === 'oauth2') {
41
+ this.log(`Client ID: ${context.client_id ?? 'N/A'}`);
42
+ }
38
43
  if (context.tenant_id) {
39
44
  this.log(`Tenant ID: ${context.tenant_id}`);
40
45
  }
41
- this.log(`Authenticated: ${context.tokens ? 'Yes' : 'No'}`);
46
+ this.log(`Authenticated: ${hasCredentials(context) ? 'Yes' : 'No'}`);
42
47
  }
43
48
  }
@@ -1,5 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
- import { getCurrentContext, listContexts } from '../../lib/config.js';
2
+ import { getAuthType, getCurrentContext, hasCredentials, listContexts } from '../../lib/config.js';
3
3
  export default class ConfigList extends Command {
4
4
  static description = 'List all configured contexts';
5
5
  static examples = ['<%= config.bin %> config list', '<%= config.bin %> config list --json'];
@@ -16,7 +16,8 @@ export default class ConfigList extends Command {
16
16
  if (flags.json) {
17
17
  this.log(JSON.stringify({
18
18
  contexts: contexts.map((c) => ({
19
- authenticated: Boolean(c.tokens),
19
+ auth_type: getAuthType(c),
20
+ authenticated: hasCredentials(c),
20
21
  client_id: c.client_id,
21
22
  environment: c.environment,
22
23
  name: c.name,
@@ -35,10 +36,14 @@ export default class ConfigList extends Command {
35
36
  for (const ctx of contexts) {
36
37
  const isCurrent = current?.name === ctx.name;
37
38
  const marker = isCurrent ? '*' : ' ';
38
- const auth = ctx.tokens ? '✓' : '✗';
39
+ const auth = hasCredentials(ctx) ? '✓' : '✗';
40
+ const authType = getAuthType(ctx);
39
41
  this.log(`${marker} ${ctx.name}`);
40
42
  this.log(` Environment: ${ctx.environment}`);
41
- this.log(` Client ID: ${ctx.client_id}`);
43
+ this.log(` Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
44
+ if (authType === 'oauth2') {
45
+ this.log(` Client ID: ${ctx.client_id ?? 'N/A'}`);
46
+ }
42
47
  this.log(` Authenticated: ${auth}`);
43
48
  if (ctx.tenant_id) {
44
49
  this.log(` Tenant ID: ${ctx.tenant_id}`);
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ConfigSetApiKey extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'api-key': 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
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,49 @@
1
+ import { password } from '@inquirer/prompts';
2
+ import { Command, Flags } from '@oclif/core';
3
+ import { getAuthType, getContext, getCurrentContext, maskApiKey, saveApiKey } from '../../lib/config.js';
4
+ export default class ConfigSetApiKey extends Command {
5
+ static description = 'Set or rotate the API key for a context (switches it to api-key auth)';
6
+ static examples = [
7
+ '<%= config.bin %> config set-api-key',
8
+ '<%= config.bin %> config set-api-key --context ci',
9
+ '<%= config.bin %> config set-api-key --context ci --api-key <key>',
10
+ ];
11
+ static flags = {
12
+ 'api-key': Flags.string({
13
+ description: 'The API key. Prompted for (hidden) if omitted.',
14
+ }),
15
+ context: Flags.string({
16
+ aliases: ['config'],
17
+ char: 'c',
18
+ description: 'Context to update, overriding the current context (alias: --config)',
19
+ env: 'ADERIS_CONTEXT',
20
+ }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(ConfigSetApiKey);
24
+ const contextName = flags.context ?? getCurrentContext()?.name;
25
+ if (!contextName) {
26
+ this.error('No context configured. Run: aderis config add');
27
+ }
28
+ const context = getContext(contextName);
29
+ if (!context) {
30
+ this.error(`Context "${contextName}" not found.`);
31
+ }
32
+ const apiKey = flags['api-key'] ??
33
+ (await password({
34
+ mask: true,
35
+ message: `API key for "${contextName}":`,
36
+ validate: (value) => (value.trim() ? true : 'API key is required'),
37
+ }));
38
+ if (!apiKey.trim()) {
39
+ this.error('API key is required');
40
+ }
41
+ const wasOAuth = getAuthType(context) === 'oauth2';
42
+ saveApiKey(contextName, apiKey.trim());
43
+ this.log(`API key set for context "${contextName}" (${maskApiKey(apiKey.trim())})`);
44
+ if (wasOAuth) {
45
+ this.log('Auth method switched from oauth2 to api-key; requests now send the x-api-key header.');
46
+ this.log(`Any stored OAuth tokens are kept but unused. Remove them with: aderis auth logout --context ${contextName}`);
47
+ }
48
+ }
49
+ }
@@ -1,6 +1,6 @@
1
1
  import { select } from '@inquirer/prompts';
2
2
  import { Args, Command } from '@oclif/core';
3
- import { getContext, listContexts, setCurrentContext } from '../../lib/config.js';
3
+ import { getContext, hasCredentials, listContexts, missingCredentialsMessage, setCurrentContext, } from '../../lib/config.js';
4
4
  export default class ConfigUse extends Command {
5
5
  static args = {
6
6
  name: Args.string({
@@ -32,9 +32,8 @@ export default class ConfigUse extends Command {
32
32
  }
33
33
  setCurrentContext(contextName);
34
34
  this.log(`Switched to context "${contextName}" (${context.environment})`);
35
- if (!context.tokens) {
36
- this.log(`\nNote: You are not logged in to this context.`);
37
- this.log(`Run: aderis auth login`);
35
+ if (!hasCredentials(context)) {
36
+ this.log(`\nNote: ${missingCredentialsMessage(context)}`);
38
37
  }
39
38
  }
40
39
  }
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
2
+ import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
+ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
@@ -39,7 +39,7 @@ export default class CreditCardAccounts extends Command {
39
39
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
40
40
  };
41
41
  static flags = {
42
- context: Flags.string({ char: 'c', description: 'Context to use' }),
42
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
43
43
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
44
44
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
45
45
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -52,7 +52,7 @@ export default class CreditCardAccounts extends Command {
52
52
  limit: Flags.string({ description: 'Items per page' }),
53
53
  include: Flags.string({ description: 'Relations to include' }),
54
54
  search: Flags.string({ description: 'Search term' }),
55
- 'sort-by': Flags.string({ description: 'Sort field' }),
55
+ 'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
56
56
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
57
57
  query: Flags.string({ description: 'Additional query params as JSON' }),
58
58
  fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
@@ -113,9 +113,9 @@ export default class CreditCardAccounts extends Command {
113
113
  }
114
114
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
115
115
  if (!context)
116
- this.error('No context configured. Run: aderis auth login --new');
117
- if (!context.tokens)
118
- this.error('Not logged in. Run: aderis auth login');
116
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
117
+ if (!hasCredentials(context))
118
+ this.error(missingCredentialsMessage(context));
119
119
  const queryParams = {};
120
120
  if (flags.page)
121
121
  queryParams.page = flags.page;
@@ -126,7 +126,7 @@ export default class CreditCardAccounts extends Command {
126
126
  if (flags.search)
127
127
  queryParams.search = flags.search;
128
128
  if (flags['sort-by'])
129
- queryParams.sortBy = flags['sort-by'];
129
+ queryParams.sortBy = normalizeSortBy(flags['sort-by']);
130
130
  if (flags.query) {
131
131
  try {
132
132
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -358,9 +358,9 @@ export default class CreditCardAccounts extends Command {
358
358
  async runImport(flags) {
359
359
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
360
360
  if (!context)
361
- this.error('No context configured. Run: aderis auth login --new');
362
- if (!context.tokens)
363
- this.error('Not logged in. Run: aderis auth login');
361
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
362
+ if (!hasCredentials(context))
363
+ this.error(missingCredentialsMessage(context));
364
364
  // Get data from file, --data, or --set flags
365
365
  let items = [];
366
366
  if (flags.file) {
@@ -570,9 +570,9 @@ export default class CreditCardAccounts extends Command {
570
570
  async runExport(flags) {
571
571
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
572
572
  if (!context)
573
- this.error('No context configured. Run: aderis auth login --new');
574
- if (!context.tokens)
575
- this.error('Not logged in. Run: aderis auth login');
573
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
574
+ if (!hasCredentials(context))
575
+ this.error(missingCredentialsMessage(context));
576
576
  const listEndpoint = ENDPOINTS['list'];
577
577
  if (!listEndpoint)
578
578
  this.error('Export requires a list endpoint');
@@ -585,7 +585,7 @@ export default class CreditCardAccounts extends Command {
585
585
  if (flags.search)
586
586
  baseParams.search = flags.search;
587
587
  if (flags['sort-by'])
588
- baseParams.sortBy = flags['sort-by'];
588
+ baseParams.sortBy = normalizeSortBy(flags['sort-by']);
589
589
  if (flags.include)
590
590
  baseParams.include = flags.include;
591
591
  if (flags.query) {
@@ -664,9 +664,9 @@ export default class CreditCardAccounts extends Command {
664
664
  async runCamt053Import(flags) {
665
665
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
666
666
  if (!context)
667
- this.error('No context configured. Run: aderis auth login --new');
668
- if (!context.tokens)
669
- this.error('Not logged in. Run: aderis auth login');
667
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
668
+ if (!hasCredentials(context))
669
+ this.error(missingCredentialsMessage(context));
670
670
  if (!flags.file) {
671
671
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
672
672
  }
@@ -754,9 +754,9 @@ export default class CreditCardAccounts extends Command {
754
754
  }
755
755
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
756
756
  if (!context)
757
- this.error('No context configured. Run: aderis auth login --new');
758
- if (!context.tokens)
759
- this.error('Not logged in. Run: aderis auth login');
757
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
758
+ if (!hasCredentials(context))
759
+ this.error(missingCredentialsMessage(context));
760
760
  // Get data from file, --data, or --set flags
761
761
  let items = [];
762
762
  if (flags.file) {
@@ -1,26 +1,29 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
2
+ import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
+ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  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-from-orders", "create-from-invoice", "get", "update", "delete", "pdf", "submit", "approve", "cancel", "deliver", "ship", "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}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "create-from-orders", "create-from-invoice", "create-invoice", "get", "update", "delete", "pdf", "submit", "approve", "cancel", "replace", "deliver", "ship", "send", "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/delivery-notes', summary: 'Get all delivery notes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'lineItems'] },
12
+ 'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'lineItems'] },
13
13
  'create-from-orders': { method: 'POST', path: '/v1/delivery-notes/create-from-orders', summary: 'Create a DRAFT delivery note from several sales orders', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderIds'] },
14
14
  'create-from-invoice': { method: 'POST', path: '/v1/delivery-notes/create-from-invoice', summary: 'Create a DRAFT delivery note from a sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
+ 'create-invoice': { method: 'POST', path: '/v1/delivery-notes/create-invoice', summary: 'Create ONE draft sales invoice from several delivery notes (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['deliveryNoteIds'] },
15
16
  'get': { method: 'GET', path: '/v1/delivery-notes/{id}', summary: 'Get a delivery note by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'removeFileIds'] },
17
+ 'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'removeFileIds'] },
17
18
  'delete': { method: 'DELETE', path: '/v1/delivery-notes/{id}', summary: 'Delete a delivery note', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
19
  'pdf': { method: 'GET', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF using the configured PDF processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
20
  'submit': { method: 'POST', path: '/v1/delivery-notes/{id}/submit', summary: 'Submit a delivery note for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
21
  'approve': { method: 'POST', path: '/v1/delivery-notes/{id}/approve', summary: 'Approve a delivery note (DRAFT or PENDING_APPROVAL → APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
- 'cancel': { method: 'POST', path: '/v1/delivery-notes/{id}/cancel', summary: 'Cancel a delivery note (DRAFT / PENDING_APPROVAL / APPROVED → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
+ 'cancel': { method: 'POST', path: '/v1/delivery-notes/{id}/cancel', summary: 'Cancel a delivery note (DRAFT / PENDING_APPROVAL / APPROVED / SHIPPED / DELIVERED → CANCELLED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
+ 'replace': { method: 'POST', path: '/v1/delivery-notes/{id}/replace', summary: 'Replace a delivery note (APPROVED / SHIPPED / DELIVERED / CANCELLED → REPLACED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
24
  'deliver': { method: 'POST', path: '/v1/delivery-notes/{id}/deliver', summary: 'Mark a delivery note as delivered (SHIPPED → DELIVERED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
25
  'ship': { method: 'POST', path: '/v1/delivery-notes/{id}/ship', summary: 'Ship a delivery note (APPROVED → SHIPPED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lineItems'] },
26
+ 'send': { method: 'POST', path: '/v1/delivery-notes/{id}/send', summary: 'Send a delivery note to its recipient by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['emailCc', 'emailBcc'] },
24
27
  'create-line-items': { method: 'POST', path: '/v1/delivery-notes/{noteId}/line-items', summary: 'Add a line item to a delivery note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderLineItems'] },
25
28
  'update-line-items-reorder': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['items'] },
26
29
  'update-line-items': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Update a line item on a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderLineItems'] },
@@ -52,7 +55,7 @@ export default class DeliveryNotes extends Command {
52
55
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
53
56
  };
54
57
  static flags = {
55
- context: Flags.string({ char: 'c', description: 'Context to use' }),
58
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
56
59
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
57
60
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
58
61
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -66,7 +69,7 @@ export default class DeliveryNotes extends Command {
66
69
  limit: Flags.string({ description: 'Items per page' }),
67
70
  include: Flags.string({ description: 'Relations to include' }),
68
71
  search: Flags.string({ description: 'Search term' }),
69
- 'sort-by': Flags.string({ description: 'Sort field' }),
72
+ 'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
70
73
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
71
74
  query: Flags.string({ description: 'Additional query params as JSON' }),
72
75
  fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
@@ -127,9 +130,9 @@ export default class DeliveryNotes extends Command {
127
130
  }
128
131
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
129
132
  if (!context)
130
- this.error('No context configured. Run: aderis auth login --new');
131
- if (!context.tokens)
132
- this.error('Not logged in. Run: aderis auth login');
133
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
134
+ if (!hasCredentials(context))
135
+ this.error(missingCredentialsMessage(context));
133
136
  const queryParams = {};
134
137
  if (flags.page)
135
138
  queryParams.page = flags.page;
@@ -140,7 +143,7 @@ export default class DeliveryNotes extends Command {
140
143
  if (flags.search)
141
144
  queryParams.search = flags.search;
142
145
  if (flags['sort-by'])
143
- queryParams.sortBy = flags['sort-by'];
146
+ queryParams.sortBy = normalizeSortBy(flags['sort-by']);
144
147
  if (flags.query) {
145
148
  try {
146
149
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -372,9 +375,9 @@ export default class DeliveryNotes extends Command {
372
375
  async runImport(flags) {
373
376
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
374
377
  if (!context)
375
- this.error('No context configured. Run: aderis auth login --new');
376
- if (!context.tokens)
377
- this.error('Not logged in. Run: aderis auth login');
378
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
379
+ if (!hasCredentials(context))
380
+ this.error(missingCredentialsMessage(context));
378
381
  // Get data from file, --data, or --set flags
379
382
  let items = [];
380
383
  if (flags.file) {
@@ -584,9 +587,9 @@ export default class DeliveryNotes extends Command {
584
587
  async runExport(flags) {
585
588
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
586
589
  if (!context)
587
- this.error('No context configured. Run: aderis auth login --new');
588
- if (!context.tokens)
589
- this.error('Not logged in. Run: aderis auth login');
590
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
591
+ if (!hasCredentials(context))
592
+ this.error(missingCredentialsMessage(context));
590
593
  const listEndpoint = ENDPOINTS['list'];
591
594
  if (!listEndpoint)
592
595
  this.error('Export requires a list endpoint');
@@ -599,7 +602,7 @@ export default class DeliveryNotes extends Command {
599
602
  if (flags.search)
600
603
  baseParams.search = flags.search;
601
604
  if (flags['sort-by'])
602
- baseParams.sortBy = flags['sort-by'];
605
+ baseParams.sortBy = normalizeSortBy(flags['sort-by']);
603
606
  if (flags.include)
604
607
  baseParams.include = flags.include;
605
608
  if (flags.query) {
@@ -678,9 +681,9 @@ export default class DeliveryNotes extends Command {
678
681
  async runCamt053Import(flags) {
679
682
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
680
683
  if (!context)
681
- this.error('No context configured. Run: aderis auth login --new');
682
- if (!context.tokens)
683
- this.error('Not logged in. Run: aderis auth login');
684
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
685
+ if (!hasCredentials(context))
686
+ this.error(missingCredentialsMessage(context));
684
687
  if (!flags.file) {
685
688
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
686
689
  }
@@ -768,9 +771,9 @@ export default class DeliveryNotes extends Command {
768
771
  }
769
772
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
770
773
  if (!context)
771
- this.error('No context configured. Run: aderis auth login --new');
772
- if (!context.tokens)
773
- this.error('Not logged in. Run: aderis auth login');
774
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
775
+ if (!hasCredentials(context))
776
+ this.error(missingCredentialsMessage(context));
774
777
  // Get data from file, --data, or --set flags
775
778
  let items = [];
776
779
  if (flags.file) {
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
- import { apiRequest, ApiError } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
2
+ import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
+ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
@@ -29,7 +29,7 @@ export default class DunningConfigs extends Command {
29
29
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
30
30
  };
31
31
  static flags = {
32
- context: Flags.string({ char: 'c', description: 'Context to use' }),
32
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
33
33
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
34
34
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
35
35
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -40,7 +40,7 @@ export default class DunningConfigs extends Command {
40
40
  limit: Flags.string({ description: 'Items per page' }),
41
41
  include: Flags.string({ description: 'Relations to include' }),
42
42
  search: Flags.string({ description: 'Search term' }),
43
- 'sort-by': Flags.string({ description: 'Sort field' }),
43
+ 'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
44
44
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
45
45
  query: Flags.string({ description: 'Additional query params as JSON' }),
46
46
  fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
@@ -101,9 +101,9 @@ export default class DunningConfigs extends Command {
101
101
  }
102
102
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
103
103
  if (!context)
104
- this.error('No context configured. Run: aderis auth login --new');
105
- if (!context.tokens)
106
- this.error('Not logged in. Run: aderis auth login');
104
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
105
+ if (!hasCredentials(context))
106
+ this.error(missingCredentialsMessage(context));
107
107
  const queryParams = {};
108
108
  if (flags.page)
109
109
  queryParams.page = flags.page;
@@ -114,7 +114,7 @@ export default class DunningConfigs extends Command {
114
114
  if (flags.search)
115
115
  queryParams.search = flags.search;
116
116
  if (flags['sort-by'])
117
- queryParams.sortBy = flags['sort-by'];
117
+ queryParams.sortBy = normalizeSortBy(flags['sort-by']);
118
118
  if (flags.query) {
119
119
  try {
120
120
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -346,9 +346,9 @@ export default class DunningConfigs extends Command {
346
346
  async runImport(flags) {
347
347
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
348
348
  if (!context)
349
- this.error('No context configured. Run: aderis auth login --new');
350
- if (!context.tokens)
351
- this.error('Not logged in. Run: aderis auth login');
349
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
350
+ if (!hasCredentials(context))
351
+ this.error(missingCredentialsMessage(context));
352
352
  // Get data from file, --data, or --set flags
353
353
  let items = [];
354
354
  if (flags.file) {
@@ -558,9 +558,9 @@ export default class DunningConfigs extends Command {
558
558
  async runExport(flags) {
559
559
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
560
560
  if (!context)
561
- this.error('No context configured. Run: aderis auth login --new');
562
- if (!context.tokens)
563
- this.error('Not logged in. Run: aderis auth login');
561
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
562
+ if (!hasCredentials(context))
563
+ this.error(missingCredentialsMessage(context));
564
564
  const listEndpoint = ENDPOINTS['list'];
565
565
  if (!listEndpoint)
566
566
  this.error('Export requires a list endpoint');
@@ -573,7 +573,7 @@ export default class DunningConfigs extends Command {
573
573
  if (flags.search)
574
574
  baseParams.search = flags.search;
575
575
  if (flags['sort-by'])
576
- baseParams.sortBy = flags['sort-by'];
576
+ baseParams.sortBy = normalizeSortBy(flags['sort-by']);
577
577
  if (flags.include)
578
578
  baseParams.include = flags.include;
579
579
  if (flags.query) {
@@ -652,9 +652,9 @@ export default class DunningConfigs extends Command {
652
652
  async runCamt053Import(flags) {
653
653
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
654
654
  if (!context)
655
- this.error('No context configured. Run: aderis auth login --new');
656
- if (!context.tokens)
657
- this.error('Not logged in. Run: aderis auth login');
655
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
656
+ if (!hasCredentials(context))
657
+ this.error(missingCredentialsMessage(context));
658
658
  if (!flags.file) {
659
659
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
660
660
  }
@@ -742,9 +742,9 @@ export default class DunningConfigs extends Command {
742
742
  }
743
743
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
744
744
  if (!context)
745
- this.error('No context configured. Run: aderis auth login --new');
746
- if (!context.tokens)
747
- this.error('Not logged in. Run: aderis auth login');
745
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
746
+ if (!hasCredentials(context))
747
+ this.error(missingCredentialsMessage(context));
748
748
  // Get data from file, --data, or --set flags
749
749
  let items = [];
750
750
  if (flags.file) {