@aderis-erp/cli 1.1.4 → 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.
- package/dist/commands/access-groups.js +42 -8
- package/dist/commands/account-transaction-allocations.js +38 -4
- package/dist/commands/account-transactions.d.ts +2 -0
- package/dist/commands/account-transactions.js +68 -32
- package/dist/commands/accounts.js +46 -12
- package/dist/commands/agent-platform.js +37 -3
- package/dist/commands/agent-runs.js +49 -15
- package/dist/commands/analytics.js +39 -5
- package/dist/commands/api-clients.js +47 -13
- package/dist/commands/api-keys.js +40 -6
- package/dist/commands/async-jobs.js +45 -11
- package/dist/commands/audit-log.js +38 -4
- package/dist/commands/bank-accounts.js +54 -20
- package/dist/commands/bank-integrations.js +56 -22
- package/dist/commands/business-partners.js +77 -43
- package/dist/commands/chat.js +43 -9
- package/dist/commands/credit-card-accounts.js +57 -23
- package/dist/commands/currencies.js +37 -3
- package/dist/commands/custody-accounts.js +41 -7
- package/dist/commands/dashboards.js +52 -18
- package/dist/commands/delivery-notes.js +69 -35
- package/dist/commands/document-checksums.d.ts +1 -0
- package/dist/commands/document-checksums.js +38 -3
- package/dist/commands/dunning-configs.js +41 -7
- package/dist/commands/dunning-reminders.js +44 -10
- package/dist/commands/dunning-runs.js +37 -3
- package/dist/commands/email-transactions.js +39 -5
- package/dist/commands/employees.js +77 -43
- package/dist/commands/entity-configurations.js +39 -5
- package/dist/commands/event-mapping-rules.js +37 -3
- package/dist/commands/event-processor-mappings.js +41 -7
- package/dist/commands/files.js +48 -14
- package/dist/commands/financial-instrument-transactions.d.ts +56 -0
- package/dist/commands/financial-instrument-transactions.js +1369 -0
- package/dist/commands/financial-instrument-types.js +41 -7
- package/dist/commands/financial-instruments.js +41 -7
- package/dist/commands/health.js +41 -7
- package/dist/commands/imports.js +39 -5
- package/dist/commands/inbox.js +42 -8
- package/dist/commands/integrations.js +39 -5
- package/dist/commands/item-categories.js +41 -7
- package/dist/commands/items.js +42 -8
- package/dist/commands/journal-lines.js +37 -3
- package/dist/commands/journals.js +61 -27
- package/dist/commands/kpis.js +44 -10
- package/dist/commands/ledgers.js +83 -49
- package/dist/commands/my-account.js +54 -20
- package/dist/commands/notifications.js +37 -3
- package/dist/commands/office-locations.js +41 -7
- package/dist/commands/payment-methods.js +46 -12
- package/dist/commands/payment-runs.js +54 -20
- package/dist/commands/payment-terms.js +42 -8
- package/dist/commands/payments.js +43 -9
- package/dist/commands/payroll-runs.js +71 -37
- package/dist/commands/permissions.js +37 -3
- package/dist/commands/price-lists.js +48 -14
- package/dist/commands/processor-mappings.js +40 -6
- package/dist/commands/processors.js +46 -12
- package/dist/commands/purchase-order-types.js +41 -7
- package/dist/commands/purchase-orders.js +60 -26
- package/dist/commands/reports.js +43 -8
- package/dist/commands/roles.js +40 -6
- package/dist/commands/salary-types.js +41 -7
- package/dist/commands/sales-invoices.js +85 -51
- package/dist/commands/sales-orders.js +69 -35
- package/dist/commands/saved-views.js +39 -5
- package/dist/commands/scheduled-processor-mappings.js +41 -7
- package/dist/commands/schedules.js +45 -11
- package/dist/commands/settings.js +38 -4
- package/dist/commands/tags.js +42 -8
- package/dist/commands/tax-codes.js +44 -10
- package/dist/commands/tax-matrix.js +41 -7
- package/dist/commands/templates.js +49 -15
- package/dist/commands/tenants.js +44 -10
- package/dist/commands/tools.js +38 -4
- package/dist/commands/users.js +58 -24
- package/dist/commands/vat-declarations.js +61 -27
- package/dist/commands/vendor-invoices.js +81 -47
- package/dist/commands/webhooks.js +43 -9
- package/dist/commands/work-summary.js +37 -3
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/production-guard.js +2 -0
- package/oclif.manifest.json +199 -2
- 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,7 +12,7 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["usage"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'usage': { method: 'GET', path: '/v1/agent-platform/usage', summary: 'Fetch tenant LLM usage report (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'usage': { method: 'GET', path: '/v1/agent-platform/usage', summary: 'Fetch tenant LLM usage report (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
15
16
|
};
|
|
16
17
|
export default class AgentPlatform extends Command {
|
|
17
18
|
static description = 'Manage agent platform';
|
|
@@ -26,7 +27,7 @@ export default class AgentPlatform extends Command {
|
|
|
26
27
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
27
28
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
28
29
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
29
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
30
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
30
31
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
31
32
|
page: Flags.string({ description: 'Page number' }),
|
|
32
33
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -157,6 +158,17 @@ export default class AgentPlatform extends Command {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
}
|
|
161
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
162
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
163
|
+
for (const param of endpoint.specParams) {
|
|
164
|
+
const value = flags[param.flag];
|
|
165
|
+
if (value === undefined || value === '') {
|
|
166
|
+
if (param.required)
|
|
167
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
queryParams[param.name] = value;
|
|
171
|
+
}
|
|
160
172
|
let data;
|
|
161
173
|
if (endpoint.isMultipart) {
|
|
162
174
|
if (!flags.file)
|
|
@@ -177,6 +189,25 @@ export default class AgentPlatform extends Command {
|
|
|
177
189
|
}
|
|
178
190
|
data = form;
|
|
179
191
|
}
|
|
192
|
+
else if (endpoint.bodyMediaType) {
|
|
193
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
194
|
+
// verbatim, under the content type the endpoint asks for.
|
|
195
|
+
if (flags.file) {
|
|
196
|
+
const fsModule = await import('node:fs');
|
|
197
|
+
try {
|
|
198
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (flags.data) {
|
|
205
|
+
data = flags.data;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
180
211
|
else if (endpoint.hasBody) {
|
|
181
212
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
182
213
|
// input file — skip the read-as-body branch.
|
|
@@ -272,7 +303,7 @@ export default class AgentPlatform extends Command {
|
|
|
272
303
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
273
304
|
}
|
|
274
305
|
else if (data) {
|
|
275
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
306
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
276
307
|
}
|
|
277
308
|
process.stderr.write('\n');
|
|
278
309
|
}
|
|
@@ -286,6 +317,7 @@ export default class AgentPlatform extends Command {
|
|
|
286
317
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
287
318
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
288
319
|
data,
|
|
320
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
289
321
|
contextName: flags.context,
|
|
290
322
|
binary: true,
|
|
291
323
|
});
|
|
@@ -311,6 +343,7 @@ export default class AgentPlatform extends Command {
|
|
|
311
343
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
312
344
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
313
345
|
data,
|
|
346
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
314
347
|
contextName: flags.context,
|
|
315
348
|
binary: true,
|
|
316
349
|
});
|
|
@@ -331,6 +364,7 @@ export default class AgentPlatform extends Command {
|
|
|
331
364
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
332
365
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
333
366
|
data,
|
|
367
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
334
368
|
contextName: flags.context,
|
|
335
369
|
acceptAny: true,
|
|
336
370
|
});
|
|
@@ -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,19 +12,19 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["sources", "sources-counts", "sources-email", "get-documents", "documents-process-anyway", "documents-discard", "documents-send-to-inbox", "documents-attach", "get-runs", "get-runs-runId", "runs-answer", "runs-restart", "runs-discard"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'sources': { method: 'GET', path: '/v1/agent-runs/sources', summary: 'List v2 sources: arrivals with their files, documents, results and actions (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'sources-counts': { method: 'GET', path: '/v1/agent-runs/sources/counts', summary: 'Counts for the v2 status tabs (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'sources-email': { method: 'GET', path: '/v1/agent-runs/sources/{sourceId}/email', summary: 'The email behind a v2 source, for the View email dialog (agents-platform pass-through).', pathParams: ['sourceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'get-documents': { method: 'GET', path: '/v1/agent-runs/documents/{documentId}', summary: 'One v2 document: header, question, results, facts, timeline, files, actions, technical (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'documents-process-anyway': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/process-anyway', summary: 'Process a duplicate document anyway (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
-
'documents-discard': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/discard', summary: 'Discard a document (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
-
'documents-send-to-inbox': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/send-to-inbox', summary: 'Stop processing a document and file it in the Inbox.', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
-
'documents-attach': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/attach', summary: 'Attach a document to another record as supporting files.', pathParams: ['documentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
-
'get-runs': { method: 'GET', path: '/v1/agent-runs/runs', summary: 'List v2 runs, the Runs tab worklist (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
-
'get-runs-runId': { method: 'GET', path: '/v1/agent-runs/runs/{runId}', summary: 'One v2 run detail (agents-platform pass-through).', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
-
'runs-answer': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/answer', summary: 'Answer a run stopped for review (agents-platform pass-through).', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
25
|
-
'runs-restart': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/restart', summary: 'Restart a stopped run (agents-platform pass-through).', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
|
-
'runs-discard': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/discard', summary: 'Discard a stopped run (agents-platform pass-through).', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'sources': { method: 'GET', path: '/v1/agent-runs/sources', summary: 'List v2 sources: arrivals with their files, documents, results and actions (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'sources-counts': { method: 'GET', path: '/v1/agent-runs/sources/counts', summary: 'Counts for the v2 status tabs (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'sources-email': { method: 'GET', path: '/v1/agent-runs/sources/{sourceId}/email', summary: 'The email behind a v2 source, for the View email dialog (agents-platform pass-through).', pathParams: ['sourceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'get-documents': { method: 'GET', path: '/v1/agent-runs/documents/{documentId}', summary: 'One v2 document: header, question, results, facts, timeline, files, actions, technical (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'documents-process-anyway': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/process-anyway', summary: 'Process a duplicate document anyway (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'documents-discard': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/discard', summary: 'Discard a document (agents-platform pass-through).', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
21
|
+
'documents-send-to-inbox': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/send-to-inbox', summary: 'Stop processing a document and file it in the Inbox.', pathParams: ['documentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
22
|
+
'documents-attach': { method: 'POST', path: '/v1/agent-runs/documents/{documentId}/attach', summary: 'Attach a document to another record as supporting files.', pathParams: ['documentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
23
|
+
'get-runs': { method: 'GET', path: '/v1/agent-runs/runs', summary: 'List v2 runs, the Runs tab worklist (agents-platform pass-through).', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
|
+
'get-runs-runId': { method: 'GET', path: '/v1/agent-runs/runs/{runId}', summary: 'One v2 run detail (agents-platform pass-through).', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
25
|
+
'runs-answer': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/answer', summary: 'Answer a run stopped for review (agents-platform pass-through).', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
26
|
+
'runs-restart': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/restart', summary: 'Restart a stopped run (agents-platform pass-through).', pathParams: ['runId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
27
|
+
'runs-discard': { method: 'POST', path: '/v1/agent-runs/runs/{runId}/discard', summary: 'Discard a stopped run (agents-platform pass-through).', pathParams: ['runId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
27
28
|
};
|
|
28
29
|
export default class AgentRuns extends Command {
|
|
29
30
|
static description = 'Manage agent runs';
|
|
@@ -38,7 +39,7 @@ export default class AgentRuns 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
|
page: Flags.string({ description: 'Page number' }),
|
|
44
45
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -169,6 +170,17 @@ export default class AgentRuns extends Command {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
}
|
|
173
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
174
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
175
|
+
for (const param of endpoint.specParams) {
|
|
176
|
+
const value = flags[param.flag];
|
|
177
|
+
if (value === undefined || value === '') {
|
|
178
|
+
if (param.required)
|
|
179
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
queryParams[param.name] = value;
|
|
183
|
+
}
|
|
172
184
|
let data;
|
|
173
185
|
if (endpoint.isMultipart) {
|
|
174
186
|
if (!flags.file)
|
|
@@ -189,6 +201,25 @@ export default class AgentRuns extends Command {
|
|
|
189
201
|
}
|
|
190
202
|
data = form;
|
|
191
203
|
}
|
|
204
|
+
else if (endpoint.bodyMediaType) {
|
|
205
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
206
|
+
// verbatim, under the content type the endpoint asks for.
|
|
207
|
+
if (flags.file) {
|
|
208
|
+
const fsModule = await import('node:fs');
|
|
209
|
+
try {
|
|
210
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (flags.data) {
|
|
217
|
+
data = flags.data;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
192
223
|
else if (endpoint.hasBody) {
|
|
193
224
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
194
225
|
// input file — skip the read-as-body branch.
|
|
@@ -284,7 +315,7 @@ export default class AgentRuns extends Command {
|
|
|
284
315
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
285
316
|
}
|
|
286
317
|
else if (data) {
|
|
287
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
318
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
288
319
|
}
|
|
289
320
|
process.stderr.write('\n');
|
|
290
321
|
}
|
|
@@ -298,6 +329,7 @@ export default class AgentRuns extends Command {
|
|
|
298
329
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
299
330
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
300
331
|
data,
|
|
332
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
301
333
|
contextName: flags.context,
|
|
302
334
|
binary: true,
|
|
303
335
|
});
|
|
@@ -323,6 +355,7 @@ export default class AgentRuns extends Command {
|
|
|
323
355
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
324
356
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
325
357
|
data,
|
|
358
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
326
359
|
contextName: flags.context,
|
|
327
360
|
binary: true,
|
|
328
361
|
});
|
|
@@ -343,6 +376,7 @@ export default class AgentRuns extends Command {
|
|
|
343
376
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
344
377
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
345
378
|
data,
|
|
379
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
346
380
|
contextName: flags.context,
|
|
347
381
|
acceptAny: true,
|
|
348
382
|
});
|
|
@@ -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,9 +12,9 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["query", "views", "views-name"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'query': { method: 'POST', path: '/v1/analytics/query', summary: 'Run a structured aggregate query over a reporting view', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['measures', 'dimensions', 'filters', 'order'] },
|
|
15
|
-
'views': { method: 'GET', path: '/v1/analytics/views', summary: 'List the analytics views for discovery', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'views-name': { method: 'GET', path: '/v1/analytics/views/{name}', summary: 'Describe one analytics view with full metadata', pathParams: ['name'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'query': { method: 'POST', path: '/v1/analytics/query', summary: 'Run a structured aggregate query over a reporting view', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['measures', 'dimensions', 'filters', 'order'], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'views': { method: 'GET', path: '/v1/analytics/views', summary: 'List the analytics views for discovery', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'views-name': { method: 'GET', path: '/v1/analytics/views/{name}', summary: 'Describe one analytics view with full metadata', pathParams: ['name'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
18
|
};
|
|
18
19
|
export default class Analytics extends Command {
|
|
19
20
|
static description = 'Manage analytics';
|
|
@@ -28,7 +29,7 @@ export default class Analytics extends Command {
|
|
|
28
29
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
29
30
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
30
31
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
31
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
32
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
32
33
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
33
34
|
page: Flags.string({ description: 'Page number' }),
|
|
34
35
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -159,6 +160,17 @@ export default class Analytics extends Command {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
}
|
|
163
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
164
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
165
|
+
for (const param of endpoint.specParams) {
|
|
166
|
+
const value = flags[param.flag];
|
|
167
|
+
if (value === undefined || value === '') {
|
|
168
|
+
if (param.required)
|
|
169
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
queryParams[param.name] = value;
|
|
173
|
+
}
|
|
162
174
|
let data;
|
|
163
175
|
if (endpoint.isMultipart) {
|
|
164
176
|
if (!flags.file)
|
|
@@ -179,6 +191,25 @@ export default class Analytics extends Command {
|
|
|
179
191
|
}
|
|
180
192
|
data = form;
|
|
181
193
|
}
|
|
194
|
+
else if (endpoint.bodyMediaType) {
|
|
195
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
196
|
+
// verbatim, under the content type the endpoint asks for.
|
|
197
|
+
if (flags.file) {
|
|
198
|
+
const fsModule = await import('node:fs');
|
|
199
|
+
try {
|
|
200
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (flags.data) {
|
|
207
|
+
data = flags.data;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
182
213
|
else if (endpoint.hasBody) {
|
|
183
214
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
184
215
|
// input file — skip the read-as-body branch.
|
|
@@ -274,7 +305,7 @@ export default class Analytics extends Command {
|
|
|
274
305
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
275
306
|
}
|
|
276
307
|
else if (data) {
|
|
277
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
308
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
278
309
|
}
|
|
279
310
|
process.stderr.write('\n');
|
|
280
311
|
}
|
|
@@ -288,6 +319,7 @@ export default class Analytics extends Command {
|
|
|
288
319
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
289
320
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
290
321
|
data,
|
|
322
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
291
323
|
contextName: flags.context,
|
|
292
324
|
binary: true,
|
|
293
325
|
});
|
|
@@ -313,6 +345,7 @@ export default class Analytics extends Command {
|
|
|
313
345
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
314
346
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
315
347
|
data,
|
|
348
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
316
349
|
contextName: flags.context,
|
|
317
350
|
binary: true,
|
|
318
351
|
});
|
|
@@ -333,6 +366,7 @@ export default class Analytics extends Command {
|
|
|
333
366
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
334
367
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
335
368
|
data,
|
|
369
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
336
370
|
contextName: flags.context,
|
|
337
371
|
acceptAny: true,
|
|
338
372
|
});
|
|
@@ -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,17 +12,17 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "api-clients-disable-all", "get", "list-api-clients-secrets", "create-api-clients-secrets", "delete-api-clients-secrets", "api-clients-disable", "list-api-clients-api-keys", "create-api-clients-api-keys", "delete-api-clients-api-keys"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/auth/api-clients', summary: 'List current tenant API clients, including disabled ones', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/auth/api-clients', summary: 'Create an API client in the current tenant', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['permissions'] },
|
|
16
|
-
'api-clients-disable-all': { method: 'POST', path: '/v1/auth/api-clients/disable-all', summary: 'Disable current tenant API clients', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'get': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}', summary: 'Read one current tenant API client', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'list-api-clients-secrets': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}/secrets', summary: 'List API client secret metadata', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
-
'create-api-clients-secrets': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/secrets', summary: 'Issue an API client secret', pathParams: ['apiClientId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
-
'delete-api-clients-secrets': { method: 'DELETE', path: '/v1/auth/api-clients/{apiClientId}/secrets/{secretId}', summary: 'Revoke one API client secret', pathParams: ['apiClientId', 'secretId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
-
'api-clients-disable': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/disable', summary: 'Disable an API client and end its sessions', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
-
'list-api-clients-api-keys': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}/api-keys', summary: 'List integration API key metadata', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
-
'create-api-clients-api-keys': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/api-keys', summary: 'Issue an integration API key', pathParams: ['apiClientId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
-
'delete-api-clients-api-keys': { method: 'DELETE', path: '/v1/auth/api-clients/{apiClientId}/api-keys/{keyId}', summary: 'Revoke one integration API key', pathParams: ['apiClientId', 'keyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/auth/api-clients', summary: 'List current tenant API clients, including disabled ones', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/auth/api-clients', summary: 'Create an API client in the current tenant', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['permissions'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'api-clients-disable-all': { method: 'POST', path: '/v1/auth/api-clients/disable-all', summary: 'Disable current tenant API clients', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'get': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}', summary: 'Read one current tenant API client', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'list-api-clients-secrets': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}/secrets', summary: 'List API client secret metadata', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'create-api-clients-secrets': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/secrets', summary: 'Issue an API client secret', pathParams: ['apiClientId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
21
|
+
'delete-api-clients-secrets': { method: 'DELETE', path: '/v1/auth/api-clients/{apiClientId}/secrets/{secretId}', summary: 'Revoke one API client secret', pathParams: ['apiClientId', 'secretId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
22
|
+
'api-clients-disable': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/disable', summary: 'Disable an API client and end its sessions', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
23
|
+
'list-api-clients-api-keys': { method: 'GET', path: '/v1/auth/api-clients/{apiClientId}/api-keys', summary: 'List integration API key metadata', pathParams: ['apiClientId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
|
+
'create-api-clients-api-keys': { method: 'POST', path: '/v1/auth/api-clients/{apiClientId}/api-keys', summary: 'Issue an integration API key', pathParams: ['apiClientId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
25
|
+
'delete-api-clients-api-keys': { method: 'DELETE', path: '/v1/auth/api-clients/{apiClientId}/api-keys/{keyId}', summary: 'Revoke one integration API key', pathParams: ['apiClientId', 'keyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
25
26
|
};
|
|
26
27
|
export default class ApiClients extends Command {
|
|
27
28
|
static description = 'Manage api clients';
|
|
@@ -41,7 +42,7 @@ export default class ApiClients extends Command {
|
|
|
41
42
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
42
43
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
43
44
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
44
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
45
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
45
46
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
46
47
|
'secret-id': Flags.string({ description: 'Path param: secretId' }),
|
|
47
48
|
'key-id': Flags.string({ description: 'Path param: keyId' }),
|
|
@@ -174,6 +175,17 @@ export default class ApiClients extends Command {
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
}
|
|
178
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
179
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
180
|
+
for (const param of endpoint.specParams) {
|
|
181
|
+
const value = flags[param.flag];
|
|
182
|
+
if (value === undefined || value === '') {
|
|
183
|
+
if (param.required)
|
|
184
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
queryParams[param.name] = value;
|
|
188
|
+
}
|
|
177
189
|
let data;
|
|
178
190
|
if (endpoint.isMultipart) {
|
|
179
191
|
if (!flags.file)
|
|
@@ -194,6 +206,25 @@ export default class ApiClients extends Command {
|
|
|
194
206
|
}
|
|
195
207
|
data = form;
|
|
196
208
|
}
|
|
209
|
+
else if (endpoint.bodyMediaType) {
|
|
210
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
211
|
+
// verbatim, under the content type the endpoint asks for.
|
|
212
|
+
if (flags.file) {
|
|
213
|
+
const fsModule = await import('node:fs');
|
|
214
|
+
try {
|
|
215
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (flags.data) {
|
|
222
|
+
data = flags.data;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
197
228
|
else if (endpoint.hasBody) {
|
|
198
229
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
199
230
|
// input file — skip the read-as-body branch.
|
|
@@ -289,7 +320,7 @@ export default class ApiClients extends Command {
|
|
|
289
320
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
290
321
|
}
|
|
291
322
|
else if (data) {
|
|
292
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
323
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
293
324
|
}
|
|
294
325
|
process.stderr.write('\n');
|
|
295
326
|
}
|
|
@@ -303,6 +334,7 @@ export default class ApiClients extends Command {
|
|
|
303
334
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
304
335
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
305
336
|
data,
|
|
337
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
306
338
|
contextName: flags.context,
|
|
307
339
|
binary: true,
|
|
308
340
|
});
|
|
@@ -328,6 +360,7 @@ export default class ApiClients extends Command {
|
|
|
328
360
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
329
361
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
330
362
|
data,
|
|
363
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
331
364
|
contextName: flags.context,
|
|
332
365
|
binary: true,
|
|
333
366
|
});
|
|
@@ -348,6 +381,7 @@ export default class ApiClients extends Command {
|
|
|
348
381
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
349
382
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
350
383
|
data,
|
|
384
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
351
385
|
contextName: flags.context,
|
|
352
386
|
acceptAny: true,
|
|
353
387
|
});
|
|
@@ -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,10 +12,10 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "api-keys-rotate", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/auth/api-keys', summary: 'List the tenant\'s API keys', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/auth/api-keys', summary: 'Create an API key for the tenant', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['permissions'] },
|
|
16
|
-
'api-keys-rotate': { method: 'POST', path: '/v1/auth/api-keys/{apiKeyId}/rotate', summary: 'Rotate an API key', pathParams: ['apiKeyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'delete': { method: 'DELETE', path: '/v1/auth/api-keys/{apiKeyId}', summary: 'Revoke an API key', pathParams: ['apiKeyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/auth/api-keys', summary: 'List the tenant\'s API keys', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/auth/api-keys', summary: 'Create an API key for the tenant', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['permissions'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'api-keys-rotate': { method: 'POST', path: '/v1/auth/api-keys/{apiKeyId}/rotate', summary: 'Rotate an API key', pathParams: ['apiKeyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'delete': { method: 'DELETE', path: '/v1/auth/api-keys/{apiKeyId}', summary: 'Revoke an API key', pathParams: ['apiKeyId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
19
|
};
|
|
19
20
|
export default class ApiKeys extends Command {
|
|
20
21
|
static description = 'Manage api keys';
|
|
@@ -33,7 +34,7 @@ export default class ApiKeys extends Command {
|
|
|
33
34
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
34
35
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
35
36
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
36
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
37
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
37
38
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
38
39
|
page: Flags.string({ description: 'Page number' }),
|
|
39
40
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -164,6 +165,17 @@ export default class ApiKeys extends Command {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
}
|
|
168
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
169
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
170
|
+
for (const param of endpoint.specParams) {
|
|
171
|
+
const value = flags[param.flag];
|
|
172
|
+
if (value === undefined || value === '') {
|
|
173
|
+
if (param.required)
|
|
174
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
queryParams[param.name] = value;
|
|
178
|
+
}
|
|
167
179
|
let data;
|
|
168
180
|
if (endpoint.isMultipart) {
|
|
169
181
|
if (!flags.file)
|
|
@@ -184,6 +196,25 @@ export default class ApiKeys extends Command {
|
|
|
184
196
|
}
|
|
185
197
|
data = form;
|
|
186
198
|
}
|
|
199
|
+
else if (endpoint.bodyMediaType) {
|
|
200
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
201
|
+
// verbatim, under the content type the endpoint asks for.
|
|
202
|
+
if (flags.file) {
|
|
203
|
+
const fsModule = await import('node:fs');
|
|
204
|
+
try {
|
|
205
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else if (flags.data) {
|
|
212
|
+
data = flags.data;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
187
218
|
else if (endpoint.hasBody) {
|
|
188
219
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
189
220
|
// input file — skip the read-as-body branch.
|
|
@@ -279,7 +310,7 @@ export default class ApiKeys extends Command {
|
|
|
279
310
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
280
311
|
}
|
|
281
312
|
else if (data) {
|
|
282
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
313
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
283
314
|
}
|
|
284
315
|
process.stderr.write('\n');
|
|
285
316
|
}
|
|
@@ -293,6 +324,7 @@ export default class ApiKeys extends Command {
|
|
|
293
324
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
294
325
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
295
326
|
data,
|
|
327
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
296
328
|
contextName: flags.context,
|
|
297
329
|
binary: true,
|
|
298
330
|
});
|
|
@@ -318,6 +350,7 @@ export default class ApiKeys extends Command {
|
|
|
318
350
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
319
351
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
320
352
|
data,
|
|
353
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
321
354
|
contextName: flags.context,
|
|
322
355
|
binary: true,
|
|
323
356
|
});
|
|
@@ -338,6 +371,7 @@ export default class ApiKeys extends Command {
|
|
|
338
371
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
339
372
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
340
373
|
data,
|
|
374
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
341
375
|
contextName: flags.context,
|
|
342
376
|
acceptAny: true,
|
|
343
377
|
});
|