@aderis-erp/cli 1.1.3 → 1.1.5

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 (99) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/access-groups.js +42 -8
  3. package/dist/commands/account-transaction-allocations.js +38 -4
  4. package/dist/commands/account-transactions.d.ts +2 -0
  5. package/dist/commands/account-transactions.js +68 -32
  6. package/dist/commands/accounts.js +46 -12
  7. package/dist/commands/agent-platform.js +37 -3
  8. package/dist/commands/agent-runs.js +49 -15
  9. package/dist/commands/analytics.js +39 -5
  10. package/dist/commands/api-clients.d.ts +58 -0
  11. package/dist/commands/api-clients.js +1375 -0
  12. package/dist/commands/api-keys.js +40 -6
  13. package/dist/commands/async-jobs.js +45 -11
  14. package/dist/commands/audit-log.js +38 -4
  15. package/dist/commands/bank-accounts.js +54 -20
  16. package/dist/commands/bank-integrations.js +56 -22
  17. package/dist/commands/business-partners.js +77 -43
  18. package/dist/commands/chat.js +43 -9
  19. package/dist/commands/credit-card-accounts.d.ts +4 -0
  20. package/dist/commands/credit-card-accounts.js +64 -25
  21. package/dist/commands/currencies.d.ts +56 -0
  22. package/dist/commands/currencies.js +1361 -0
  23. package/dist/commands/custody-accounts.d.ts +56 -0
  24. package/dist/commands/custody-accounts.js +1369 -0
  25. package/dist/commands/{integration-identities.d.ts → dashboards.d.ts} +3 -3
  26. package/dist/commands/dashboards.js +1381 -0
  27. package/dist/commands/delivery-notes.js +69 -35
  28. package/dist/commands/document-checksums.d.ts +57 -0
  29. package/dist/commands/{integration-identities.js → document-checksums.js} +48 -21
  30. package/dist/commands/dunning-configs.js +41 -7
  31. package/dist/commands/dunning-reminders.js +44 -10
  32. package/dist/commands/dunning-runs.js +37 -3
  33. package/dist/commands/email-transactions.js +39 -5
  34. package/dist/commands/employees.js +77 -43
  35. package/dist/commands/entity-configurations.js +39 -5
  36. package/dist/commands/event-mapping-rules.js +37 -3
  37. package/dist/commands/event-processor-mappings.js +41 -7
  38. package/dist/commands/files.js +48 -14
  39. package/dist/commands/financial-instrument-transactions.d.ts +56 -0
  40. package/dist/commands/financial-instrument-transactions.js +1369 -0
  41. package/dist/commands/financial-instrument-types.d.ts +56 -0
  42. package/dist/commands/financial-instrument-types.js +1369 -0
  43. package/dist/commands/financial-instruments.d.ts +56 -0
  44. package/dist/commands/financial-instruments.js +1369 -0
  45. package/dist/commands/health.js +41 -7
  46. package/dist/commands/imports.js +39 -5
  47. package/dist/commands/inbox.js +42 -8
  48. package/dist/commands/integrations.js +39 -5
  49. package/dist/commands/item-categories.js +41 -7
  50. package/dist/commands/items.js +42 -8
  51. package/dist/commands/journal-lines.js +37 -3
  52. package/dist/commands/journals.js +61 -27
  53. package/dist/commands/{workflows.d.ts → kpis.d.ts} +1 -1
  54. package/dist/commands/kpis.js +1371 -0
  55. package/dist/commands/ledgers.js +83 -49
  56. package/dist/commands/my-account.js +54 -20
  57. package/dist/commands/notifications.d.ts +56 -0
  58. package/dist/commands/notifications.js +1360 -0
  59. package/dist/commands/office-locations.js +41 -7
  60. package/dist/commands/payment-methods.js +46 -12
  61. package/dist/commands/payment-runs.js +54 -20
  62. package/dist/commands/payment-terms.js +42 -8
  63. package/dist/commands/payments.js +43 -9
  64. package/dist/commands/payroll-runs.js +71 -37
  65. package/dist/commands/permissions.js +37 -3
  66. package/dist/commands/price-lists.js +48 -14
  67. package/dist/commands/processor-mappings.js +40 -6
  68. package/dist/commands/processors.js +46 -12
  69. package/dist/commands/purchase-order-types.js +41 -7
  70. package/dist/commands/purchase-orders.js +60 -26
  71. package/dist/commands/reports.js +43 -8
  72. package/dist/commands/roles.js +40 -6
  73. package/dist/commands/salary-types.js +41 -7
  74. package/dist/commands/sales-invoices.js +85 -51
  75. package/dist/commands/sales-orders.js +69 -35
  76. package/dist/commands/saved-views.js +39 -5
  77. package/dist/commands/scheduled-processor-mappings.js +41 -7
  78. package/dist/commands/schedules.js +45 -11
  79. package/dist/commands/settings.d.ts +56 -0
  80. package/dist/commands/settings.js +1361 -0
  81. package/dist/commands/tags.js +42 -8
  82. package/dist/commands/tax-codes.js +44 -10
  83. package/dist/commands/tax-matrix.js +41 -7
  84. package/dist/commands/templates.js +50 -15
  85. package/dist/commands/tenants.js +44 -10
  86. package/dist/commands/tools.js +38 -4
  87. package/dist/commands/users.js +58 -24
  88. package/dist/commands/vat-declarations.js +61 -27
  89. package/dist/commands/vendor-invoices.js +82 -47
  90. package/dist/commands/webhooks.js +43 -9
  91. package/dist/commands/work-summary.d.ts +56 -0
  92. package/dist/commands/{workflows.js → work-summary.js} +49 -18
  93. package/dist/lib/auth/index.js +5 -0
  94. package/dist/lib/paths.d.ts +8 -0
  95. package/dist/lib/paths.js +16 -0
  96. package/dist/lib/production-guard.d.ts +42 -0
  97. package/dist/lib/production-guard.js +94 -0
  98. package/oclif.manifest.json +2142 -333
  99. package/package.json +9 -3
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,12 +12,12 @@ const SERVER_IMPORTS = ["import"];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "import"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/items', summary: 'Get all items with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/items', summary: 'Create a new item', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'] },
16
- 'get': { method: 'GET', path: '/v1/items/{id}', summary: 'Get an item by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'update': { method: 'PATCH', path: '/v1/items/{id}', summary: 'Update an item', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'] },
18
- 'delete': { method: 'DELETE', path: '/v1/items/{id}', summary: 'Delete an item', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
19
- 'import': { method: 'POST', path: '/v1/items/import', summary: 'Import items', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/items', summary: 'Get all items with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/items', summary: 'Create a new item', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
17
+ 'get': { method: 'GET', path: '/v1/items/{id}', summary: 'Get an item by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'update': { method: 'PATCH', path: '/v1/items/{id}', summary: 'Update an item', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
19
+ 'delete': { method: 'DELETE', path: '/v1/items/{id}', summary: 'Delete an item', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
+ 'import': { method: 'POST', path: '/v1/items/import', summary: 'Import items', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
20
21
  };
21
22
  export default class Items extends Command {
22
23
  static description = 'Manage items';
@@ -38,7 +39,7 @@ export default class Items extends Command {
38
39
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
39
40
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
40
41
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
41
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
42
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
42
43
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
43
44
  mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
44
45
  reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
@@ -173,6 +174,17 @@ export default class Items extends Command {
173
174
  }
174
175
  }
175
176
  }
177
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
178
+ // have them: their body is not JSON, so --set cannot carry a value there.
179
+ for (const param of endpoint.specParams) {
180
+ const value = flags[param.flag];
181
+ if (value === undefined || value === '') {
182
+ if (param.required)
183
+ this.error(`Missing required flag: --${param.flag}`);
184
+ continue;
185
+ }
186
+ queryParams[param.name] = value;
187
+ }
176
188
  let data;
177
189
  if (endpoint.isMultipart) {
178
190
  if (!flags.file)
@@ -193,6 +205,25 @@ export default class Items extends Command {
193
205
  }
194
206
  data = form;
195
207
  }
208
+ else if (endpoint.bodyMediaType) {
209
+ // The body is not JSON (today only application/x-ndjson): it is sent
210
+ // verbatim, under the content type the endpoint asks for.
211
+ if (flags.file) {
212
+ const fsModule = await import('node:fs');
213
+ try {
214
+ data = fsModule.readFileSync(flags.file, 'utf-8');
215
+ }
216
+ catch {
217
+ this.error(`Failed to read file: ${flags.file}`);
218
+ }
219
+ }
220
+ else if (flags.data) {
221
+ data = flags.data;
222
+ }
223
+ else {
224
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
225
+ }
226
+ }
196
227
  else if (endpoint.hasBody) {
197
228
  // For binary-response endpoints, --file is the output path, not a JSON
198
229
  // input file — skip the read-as-body branch.
@@ -288,7 +319,7 @@ export default class Items extends Command {
288
319
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
289
320
  }
290
321
  else if (data) {
291
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
322
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
292
323
  }
293
324
  process.stderr.write('\n');
294
325
  }
@@ -302,6 +333,7 @@ export default class Items extends Command {
302
333
  const binResponse = await apiRequest(endpoint.method, apiPath, {
303
334
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
304
335
  data,
336
+ contentType: endpoint.bodyMediaType || undefined,
305
337
  contextName: flags.context,
306
338
  binary: true,
307
339
  });
@@ -327,6 +359,7 @@ export default class Items extends Command {
327
359
  const raw = await apiRequest(endpoint.method, apiPath, {
328
360
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
329
361
  data,
362
+ contentType: endpoint.bodyMediaType || undefined,
330
363
  contextName: flags.context,
331
364
  binary: true,
332
365
  });
@@ -347,6 +380,7 @@ export default class Items extends Command {
347
380
  response = await apiRequest(endpoint.method, apiPath, {
348
381
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
349
382
  data,
383
+ contentType: endpoint.bodyMediaType || undefined,
350
384
  contextName: flags.context,
351
385
  acceptAny: true,
352
386
  });
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,7 +12,7 @@ const SERVER_IMPORTS = [];
11
12
  const NESTED_UPSERTS = [];
12
13
  const SUBCOMMANDS = [...["list"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/journal-lines', summary: 'List all journal lines (global view across all journals)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/journal-lines', summary: 'List all journal lines (global view across all journals)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
15
16
  };
16
17
  export default class JournalLines extends Command {
17
18
  static description = 'Manage journal lines';
@@ -29,7 +30,7 @@ export default class JournalLines extends Command {
29
30
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
30
31
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
31
32
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
32
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
33
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
33
34
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
34
35
  page: Flags.string({ description: 'Page number' }),
35
36
  limit: Flags.string({ description: 'Items per page' }),
@@ -160,6 +161,17 @@ export default class JournalLines extends Command {
160
161
  }
161
162
  }
162
163
  }
164
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
165
+ // have them: their body is not JSON, so --set cannot carry a value there.
166
+ for (const param of endpoint.specParams) {
167
+ const value = flags[param.flag];
168
+ if (value === undefined || value === '') {
169
+ if (param.required)
170
+ this.error(`Missing required flag: --${param.flag}`);
171
+ continue;
172
+ }
173
+ queryParams[param.name] = value;
174
+ }
163
175
  let data;
164
176
  if (endpoint.isMultipart) {
165
177
  if (!flags.file)
@@ -180,6 +192,25 @@ export default class JournalLines extends Command {
180
192
  }
181
193
  data = form;
182
194
  }
195
+ else if (endpoint.bodyMediaType) {
196
+ // The body is not JSON (today only application/x-ndjson): it is sent
197
+ // verbatim, under the content type the endpoint asks for.
198
+ if (flags.file) {
199
+ const fsModule = await import('node:fs');
200
+ try {
201
+ data = fsModule.readFileSync(flags.file, 'utf-8');
202
+ }
203
+ catch {
204
+ this.error(`Failed to read file: ${flags.file}`);
205
+ }
206
+ }
207
+ else if (flags.data) {
208
+ data = flags.data;
209
+ }
210
+ else {
211
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
212
+ }
213
+ }
183
214
  else if (endpoint.hasBody) {
184
215
  // For binary-response endpoints, --file is the output path, not a JSON
185
216
  // input file — skip the read-as-body branch.
@@ -275,7 +306,7 @@ export default class JournalLines extends Command {
275
306
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
276
307
  }
277
308
  else if (data) {
278
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
309
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
279
310
  }
280
311
  process.stderr.write('\n');
281
312
  }
@@ -289,6 +320,7 @@ export default class JournalLines extends Command {
289
320
  const binResponse = await apiRequest(endpoint.method, apiPath, {
290
321
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
291
322
  data,
323
+ contentType: endpoint.bodyMediaType || undefined,
292
324
  contextName: flags.context,
293
325
  binary: true,
294
326
  });
@@ -314,6 +346,7 @@ export default class JournalLines extends Command {
314
346
  const raw = await apiRequest(endpoint.method, apiPath, {
315
347
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
316
348
  data,
349
+ contentType: endpoint.bodyMediaType || undefined,
317
350
  contextName: flags.context,
318
351
  binary: true,
319
352
  });
@@ -334,6 +367,7 @@ export default class JournalLines extends Command {
334
367
  response = await apiRequest(endpoint.method, apiPath, {
335
368
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
336
369
  data,
370
+ contentType: endpoint.bodyMediaType || undefined,
337
371
  contextName: flags.context,
338
372
  acceptAny: true,
339
373
  });
@@ -1,6 +1,7 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
3
3
  import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
+ import { resolveUserPath } from '../lib/paths.js';
4
5
  // 'upsert' loops create/update from the CLI, one request per record. It exists
5
6
  // only where the API has no import endpoint of its own; those keep the name
6
7
  // 'import' and are listed in SERVER_IMPORTS.
@@ -11,31 +12,31 @@ const SERVER_IMPORTS = ["import"];
11
12
  const NESTED_UPSERTS = ["notes", "files"];
12
13
  const SUBCOMMANDS = [...["list", "create", "stats", "get", "update", "delete", "post", "approve", "cancel", "reverse", "update-lines", "get-deferrals", "create-deferrals", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "import"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
13
14
  const ENDPOINTS = {
14
- 'list': { method: 'GET', path: '/v1/journals', summary: 'List all journals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'create': { method: 'POST', path: '/v1/journals', summary: 'Create a new journal (with embedded lines)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines', 'files', 'notes'] },
16
- 'stats': { method: 'GET', path: '/v1/journals/stats', summary: 'Journal counts and debit totals per date range', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
- 'get': { method: 'GET', path: '/v1/journals/{id}', summary: 'Get a journal by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
- 'update': { method: 'PATCH', path: '/v1/journals/{id}', summary: 'Update a journal (with embedded lines upsert)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'] },
19
- 'delete': { method: 'DELETE', path: '/v1/journals/{id}', summary: 'Delete a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
20
- 'post': { method: 'POST', path: '/v1/journals/{id}/post', summary: 'Post journal (finalize)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
21
- 'approve': { method: 'POST', path: '/v1/journals/{id}/approve', summary: 'Approve a journal (transitions DRAFT to APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
22
- 'cancel': { method: 'POST', path: '/v1/journals/{id}/cancel', summary: 'Cancel a journal', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
23
- 'reverse': { method: 'POST', path: '/v1/journals/{id}/reverse', summary: 'Reverse a posted journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
24
- 'update-lines': { method: 'PATCH', path: '/v1/journals/{id}/lines/{lineId}', summary: 'Update a single journal line in place', pathParams: ['id', 'lineId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'] },
25
- 'get-deferrals': { method: 'GET', path: '/v1/journals/{id}/deferrals', summary: 'List the deferral schedules of a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
26
- 'create-deferrals': { method: 'POST', path: '/v1/journals/{id}/deferrals', summary: 'Create deferral schedules for one line of a journal (manual trigger)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['periods'] },
27
- 'list-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes', summary: 'Get all notes for a journal', pathParams: ['journalId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
28
- 'create-notes': { method: 'POST', path: '/v1/journals/{journalId}/notes', summary: 'Create a note for a journal', pathParams: ['journalId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
29
- 'get-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Get a specific note for a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
30
- 'update-notes': { method: 'PATCH', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Update a note for a journal', pathParams: ['journalId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
31
- 'delete-notes': { method: 'DELETE', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Delete a note from a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
32
- 'list-files': { method: 'GET', path: '/v1/journals/{parentId}/files', summary: 'List file attachments for a journal', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
33
- 'create-files': { method: 'POST', path: '/v1/journals/{parentId}/files', summary: 'Upload a file attachment to a journal', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
34
- 'get-files': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Get a file attachment for a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
35
- 'update-files': { method: 'PATCH', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a journal', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
36
- 'delete-files': { method: 'DELETE', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
37
- 'files-download': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
38
- 'import': { method: 'POST', path: '/v1/journals/import', summary: 'Import journals', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
+ 'list': { method: 'GET', path: '/v1/journals', summary: 'List all journals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
16
+ 'create': { method: 'POST', path: '/v1/journals', summary: 'Create a new journal (with embedded lines)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines', 'files', 'notes'], bodyMediaType: '', specParams: [] },
17
+ 'stats': { method: 'GET', path: '/v1/journals/stats', summary: 'Journal counts and debit totals per date range', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
18
+ 'get': { method: 'GET', path: '/v1/journals/{id}', summary: 'Get a journal by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
19
+ 'update': { method: 'PATCH', path: '/v1/journals/{id}', summary: 'Update a journal (with embedded lines upsert)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'], bodyMediaType: '', specParams: [] },
20
+ 'delete': { method: 'DELETE', path: '/v1/journals/{id}', summary: 'Delete a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
21
+ 'post': { method: 'POST', path: '/v1/journals/{id}/post', summary: 'Post journal (finalize)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
22
+ 'approve': { method: 'POST', path: '/v1/journals/{id}/approve', summary: 'Approve a journal (transitions DRAFT to APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
23
+ 'cancel': { method: 'POST', path: '/v1/journals/{id}/cancel', summary: 'Cancel a journal', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
24
+ 'reverse': { method: 'POST', path: '/v1/journals/{id}/reverse', summary: 'Reverse a posted journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
25
+ 'update-lines': { method: 'PATCH', path: '/v1/journals/{id}/lines/{lineId}', summary: 'Update a single journal line in place', pathParams: ['id', 'lineId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
26
+ 'get-deferrals': { method: 'GET', path: '/v1/journals/{id}/deferrals', summary: 'List the deferral schedules of a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
27
+ 'create-deferrals': { method: 'POST', path: '/v1/journals/{id}/deferrals', summary: 'Create deferral schedules for one line of a journal (manual trigger)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['periods'], bodyMediaType: '', specParams: [] },
28
+ 'list-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes', summary: 'Get all notes for a journal', pathParams: ['journalId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
29
+ 'create-notes': { method: 'POST', path: '/v1/journals/{journalId}/notes', summary: 'Create a note for a journal', pathParams: ['journalId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
30
+ 'get-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Get a specific note for a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
31
+ 'update-notes': { method: 'PATCH', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Update a note for a journal', pathParams: ['journalId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
32
+ 'delete-notes': { method: 'DELETE', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Delete a note from a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
33
+ 'list-files': { method: 'GET', path: '/v1/journals/{parentId}/files', summary: 'List file attachments for a journal', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
34
+ 'create-files': { method: 'POST', path: '/v1/journals/{parentId}/files', summary: 'Upload a file attachment to a journal', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
35
+ 'get-files': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Get a file attachment for a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
36
+ 'update-files': { method: 'PATCH', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a journal', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
37
+ 'delete-files': { method: 'DELETE', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
38
+ 'files-download': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
+ 'import': { method: 'POST', path: '/v1/journals/import', summary: 'Import journals', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
39
40
  };
40
41
  export default class Journals extends Command {
41
42
  static description = 'Manage journals';
@@ -57,7 +58,7 @@ export default class Journals extends Command {
57
58
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
58
59
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
59
60
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
60
- file: Flags.string({ char: 'f', description: 'Read request body from file' }),
61
+ file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
61
62
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
62
63
  mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
63
64
  reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
@@ -195,6 +196,17 @@ export default class Journals extends Command {
195
196
  }
196
197
  }
197
198
  }
199
+ // Flags generated from this endpoint's own parameters. Raw-body endpoints
200
+ // have them: their body is not JSON, so --set cannot carry a value there.
201
+ for (const param of endpoint.specParams) {
202
+ const value = flags[param.flag];
203
+ if (value === undefined || value === '') {
204
+ if (param.required)
205
+ this.error(`Missing required flag: --${param.flag}`);
206
+ continue;
207
+ }
208
+ queryParams[param.name] = value;
209
+ }
198
210
  let data;
199
211
  if (endpoint.isMultipart) {
200
212
  if (!flags.file)
@@ -215,6 +227,25 @@ export default class Journals extends Command {
215
227
  }
216
228
  data = form;
217
229
  }
230
+ else if (endpoint.bodyMediaType) {
231
+ // The body is not JSON (today only application/x-ndjson): it is sent
232
+ // verbatim, under the content type the endpoint asks for.
233
+ if (flags.file) {
234
+ const fsModule = await import('node:fs');
235
+ try {
236
+ data = fsModule.readFileSync(flags.file, 'utf-8');
237
+ }
238
+ catch {
239
+ this.error(`Failed to read file: ${flags.file}`);
240
+ }
241
+ }
242
+ else if (flags.data) {
243
+ data = flags.data;
244
+ }
245
+ else {
246
+ this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
247
+ }
248
+ }
218
249
  else if (endpoint.hasBody) {
219
250
  // For binary-response endpoints, --file is the output path, not a JSON
220
251
  // input file — skip the read-as-body branch.
@@ -310,7 +341,7 @@ export default class Journals extends Command {
310
341
  process.stderr.write(`Body: [multipart/form-data file upload]\n`);
311
342
  }
312
343
  else if (data) {
313
- process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
344
+ process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
314
345
  }
315
346
  process.stderr.write('\n');
316
347
  }
@@ -324,6 +355,7 @@ export default class Journals extends Command {
324
355
  const binResponse = await apiRequest(endpoint.method, apiPath, {
325
356
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
326
357
  data,
358
+ contentType: endpoint.bodyMediaType || undefined,
327
359
  contextName: flags.context,
328
360
  binary: true,
329
361
  });
@@ -349,6 +381,7 @@ export default class Journals extends Command {
349
381
  const raw = await apiRequest(endpoint.method, apiPath, {
350
382
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
351
383
  data,
384
+ contentType: endpoint.bodyMediaType || undefined,
352
385
  contextName: flags.context,
353
386
  binary: true,
354
387
  });
@@ -369,6 +402,7 @@ export default class Journals extends Command {
369
402
  response = await apiRequest(endpoint.method, apiPath, {
370
403
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
371
404
  data,
405
+ contentType: endpoint.bodyMediaType || undefined,
372
406
  contextName: flags.context,
373
407
  acceptAny: true,
374
408
  });
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Workflows extends Command {
2
+ export default class Kpis extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {