@corvio/admin-cli 0.1.0-beta.2 → 0.1.0-beta.20
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/README.md +7 -5
- package/dist/cli.js +260 -26
- package/dist/core.js +814 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Corvio Platform Administrator CLI
|
|
2
2
|
|
|
3
|
-
Official read-only CLI for bounded Corvio platform investigations. It is separate from the Workspace CLI and accepts only a short-lived `cva_` credential.
|
|
3
|
+
Official read-only CLI for bounded Corvio platform investigations. It is separate from the Workspace CLI and accepts only a short-lived `cva_` credential, supplied by environment or administrator device link.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install --global @corvio/admin-cli
|
|
7
|
-
|
|
8
|
-
corvio-admin auth status
|
|
6
|
+
npm install --global @corvio/admin-cli@beta
|
|
7
|
+
corvio-admin auth login
|
|
8
|
+
corvio-admin auth status --json
|
|
9
9
|
corvio-admin catalog --json
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Every data query requires a purpose and returns an auditable receipt. This package contains no ordinary Workspace commands. See [the administrator CLI guide](https://corvio.ai/developers/admin-cli).
|
|
12
|
+
For Agents and CI, inject `CORVIO_ADMIN_API_KEY='cva_...'`; it takes precedence over the stored device-link credential. Every data query requires a purpose and returns an auditable receipt. This package contains no ordinary Workspace commands. See [the administrator CLI guide](https://corvio.ai/developers/admin-cli).
|
|
13
|
+
|
|
14
|
+
Unknown/duplicate options, malformed bounds, and invalid JSON fail locally. API calls use the same bounded transport/runtime safety policy as the Workspace CLI. `corvio-admin skill download` reads the protected manifest first and verifies the archive SHA-256 before writing the zip.
|
package/dist/cli.js
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
ApiClient,
|
|
5
7
|
CliError,
|
|
6
8
|
EXIT,
|
|
9
|
+
assertKnownOptions,
|
|
10
|
+
assertPositionalCount,
|
|
11
|
+
clearAdminCredential,
|
|
7
12
|
listOption,
|
|
8
13
|
normalizeApiBase,
|
|
9
14
|
numberOption,
|
|
15
|
+
openBrowser,
|
|
10
16
|
option,
|
|
11
17
|
readAdminCredential,
|
|
12
18
|
readTextFile,
|
|
19
|
+
resolveNpmUpdateStatus,
|
|
13
20
|
runMain,
|
|
21
|
+
sleep,
|
|
22
|
+
storeAdminCredential,
|
|
23
|
+
writeBinaryFile,
|
|
14
24
|
writeResult,
|
|
15
25
|
} from "./core.js";
|
|
16
26
|
|
|
17
|
-
const VERSION = "0.1.0-beta.
|
|
27
|
+
const VERSION = "0.1.0-beta.20";
|
|
18
28
|
const DEFAULT_API_BASE = "https://api.corvio.ai/v1";
|
|
19
29
|
const USER_AGENT = `corvio-admin-cli/${VERSION}`;
|
|
30
|
+
const GLOBAL_OPTIONS = Object.freeze(["api-base", "json", "no-input", "help", "version"]);
|
|
31
|
+
const COMMAND_OPTIONS = Object.freeze({
|
|
32
|
+
"auth:login": ["force", "scopes"],
|
|
33
|
+
"auth:status": [],
|
|
34
|
+
"auth:logout": [],
|
|
35
|
+
"capabilities:": [],
|
|
36
|
+
"update:check": [],
|
|
37
|
+
"skill:download": ["output"],
|
|
38
|
+
"catalog:": [],
|
|
39
|
+
"query:": ["file", "purpose", "dataset", "fields", "group-by", "metrics", "filters", "order-by", "limit", "cursor"],
|
|
40
|
+
"data:query": ["file", "purpose", "dataset", "fields", "group-by", "metrics", "filters", "order-by", "limit", "cursor"],
|
|
41
|
+
"workspaces:list": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
42
|
+
"users:list": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
43
|
+
"worker-jobs:list": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
44
|
+
"runs:list": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
45
|
+
"runs:content": ["id", "purpose"],
|
|
46
|
+
"usage:events": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
47
|
+
"usage:summary": ["purpose", "workspace-id", "status", "limit", "cursor"],
|
|
48
|
+
"receipts:get": ["id"],
|
|
49
|
+
});
|
|
20
50
|
|
|
21
51
|
const HELP = `Corvio Platform Administrator CLI ${VERSION}
|
|
22
52
|
|
|
23
53
|
Usage:
|
|
24
|
-
corvio-admin auth status
|
|
54
|
+
corvio-admin auth login|status|logout
|
|
25
55
|
corvio-admin capabilities
|
|
56
|
+
corvio-admin skill download [--output <path>]
|
|
26
57
|
corvio-admin catalog
|
|
27
58
|
corvio-admin query|data query --file <request.json>
|
|
28
59
|
corvio-admin workspaces list --purpose <reason>
|
|
@@ -31,26 +62,78 @@ Usage:
|
|
|
31
62
|
corvio-admin worker-jobs list --purpose <reason>
|
|
32
63
|
corvio-admin usage events|summary --purpose <reason>
|
|
33
64
|
corvio-admin receipts get <id>
|
|
65
|
+
corvio-admin update check
|
|
34
66
|
|
|
35
67
|
Authentication:
|
|
36
|
-
|
|
68
|
+
CORVIO_ADMIN_API_KEY=cva_... takes precedence over the stored device-link login.
|
|
69
|
+
Run 'corvio-admin auth login' when no environment key is configured.
|
|
37
70
|
|
|
38
71
|
This package contains no Workspace ask, search, docs, files, sync, or share commands.
|
|
39
72
|
`;
|
|
40
73
|
|
|
41
|
-
function
|
|
42
|
-
return
|
|
74
|
+
function resourcePath(value) {
|
|
75
|
+
return encodeURIComponent(String(value));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateInvocation(options, positionals) {
|
|
79
|
+
const [command, rawAction] = positionals;
|
|
80
|
+
if (!command || command === "help" || command === "version" || options.help || options.version) return;
|
|
81
|
+
const defaultActions = {
|
|
82
|
+
update: "check",
|
|
83
|
+
workspaces: "list",
|
|
84
|
+
users: "list",
|
|
85
|
+
"worker-jobs": "list",
|
|
86
|
+
};
|
|
87
|
+
const action = ["capabilities", "catalog", "query"].includes(command)
|
|
88
|
+
? ""
|
|
89
|
+
: rawAction || defaultActions[command] || "";
|
|
90
|
+
const schema = COMMAND_OPTIONS[`${command}:${action}`];
|
|
91
|
+
if (!schema) {
|
|
92
|
+
throw new CliError(`Unknown administrator command: ${positionals.join(" ")}`, {
|
|
93
|
+
code: "unknown_command",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
assertKnownOptions(options, [...GLOBAL_OPTIONS, ...schema]);
|
|
97
|
+
const maximum = new Set(["runs:content", "receipts:get"]).has(`${command}:${action}`)
|
|
98
|
+
? 3
|
|
99
|
+
: action
|
|
100
|
+
? 2
|
|
101
|
+
: 1;
|
|
102
|
+
assertPositionalCount(positionals, maximum);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseJsonValue(value, optionName) {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(String(value));
|
|
108
|
+
} catch {
|
|
109
|
+
throw new CliError(`Option --${optionName} must contain valid JSON.`, {
|
|
110
|
+
code: "invalid_json_option",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function baseUrl(options, credential = null) {
|
|
116
|
+
return normalizeApiBase(
|
|
117
|
+
options["api-base"]
|
|
118
|
+
|| process.env.CORVIO_ADMIN_API_BASE
|
|
119
|
+
|| process.env.CORVIO_API_BASE
|
|
120
|
+
|| credential?.metadata?.api_base,
|
|
121
|
+
DEFAULT_API_BASE,
|
|
122
|
+
);
|
|
43
123
|
}
|
|
44
124
|
|
|
45
|
-
function client(options) {
|
|
46
|
-
const credential = readAdminCredential();
|
|
125
|
+
async function client(options) {
|
|
126
|
+
const credential = await readAdminCredential();
|
|
47
127
|
if (!credential) {
|
|
48
|
-
throw new CliError("Set CORVIO_ADMIN_API_KEY
|
|
128
|
+
throw new CliError("Set CORVIO_ADMIN_API_KEY or run `corvio-admin auth login`.", {
|
|
49
129
|
code: "admin_auth_required",
|
|
50
130
|
exitCode: EXIT.AUTH,
|
|
51
131
|
});
|
|
52
132
|
}
|
|
53
|
-
return
|
|
133
|
+
return {
|
|
134
|
+
api: new ApiClient({ baseUrl: baseUrl(options, credential), credential: credential.key, userAgent: USER_AGENT }),
|
|
135
|
+
credential,
|
|
136
|
+
};
|
|
54
137
|
}
|
|
55
138
|
|
|
56
139
|
function listQuery(options) {
|
|
@@ -58,13 +141,90 @@ function listQuery(options) {
|
|
|
58
141
|
purpose: option(options, "purpose", { required: true }),
|
|
59
142
|
workspace_id: options["workspace-id"],
|
|
60
143
|
status: options.status,
|
|
61
|
-
limit: numberOption(options, "limit", 100),
|
|
144
|
+
limit: numberOption(options, "limit", 100, { maximum: 200 }),
|
|
62
145
|
cursor: options.cursor,
|
|
63
146
|
};
|
|
64
147
|
}
|
|
65
148
|
|
|
149
|
+
async function authLogin(options) {
|
|
150
|
+
const current = await readAdminCredential();
|
|
151
|
+
if (current && !options.force) {
|
|
152
|
+
const api = new ApiClient({ baseUrl: baseUrl(options, current), credential: current.key, userAgent: USER_AGENT });
|
|
153
|
+
const identity = await api.request("/me");
|
|
154
|
+
writeResult({ authenticated: true, source: current.source, identity }, { json: options.json });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const requestedScopes = listOption(options, "scopes", [
|
|
158
|
+
"admin_data:read",
|
|
159
|
+
"admin_support:read",
|
|
160
|
+
"admin_costs:read",
|
|
161
|
+
]);
|
|
162
|
+
if (requestedScopes.includes("admin_content:read") && !options.force) {
|
|
163
|
+
throw new CliError(
|
|
164
|
+
"Requesting admin_content:read requires --force and should be limited to a specific Run investigation.",
|
|
165
|
+
{ code: "admin_content_scope_confirmation_required" },
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const api = new ApiClient({ baseUrl: baseUrl(options), userAgent: USER_AGENT });
|
|
169
|
+
const authorization = await api.request("/admin/auth/device-authorizations", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
auth: false,
|
|
172
|
+
body: {
|
|
173
|
+
client_name: "Corvio Platform Administrator CLI",
|
|
174
|
+
cli_version: VERSION,
|
|
175
|
+
scopes: requestedScopes,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
process.stderr.write(`Open ${authorization.verification_uri_complete}\nCode: ${authorization.user_code}\n`);
|
|
179
|
+
if (!options["no-input"]) await openBrowser(authorization.verification_uri_complete);
|
|
180
|
+
|
|
181
|
+
const expiresAt = Date.now() + Number(authorization.expires_in || 600) * 1000;
|
|
182
|
+
let intervalSeconds = Math.max(5, Number(authorization.interval || 5));
|
|
183
|
+
let token;
|
|
184
|
+
while (Date.now() < expiresAt) {
|
|
185
|
+
await sleep(intervalSeconds * 1000);
|
|
186
|
+
try {
|
|
187
|
+
token = await api.request("/admin/auth/device-token", {
|
|
188
|
+
method: "POST",
|
|
189
|
+
auth: false,
|
|
190
|
+
body: { device_code: authorization.device_code },
|
|
191
|
+
});
|
|
192
|
+
break;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (!(error instanceof CliError)) throw error;
|
|
195
|
+
if (error.code === "authorization_pending") continue;
|
|
196
|
+
if (error.code === "slow_down") {
|
|
197
|
+
intervalSeconds = Math.min(30, intervalSeconds + 5);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!token?.secret || token.audience !== "platform_admin") {
|
|
204
|
+
throw new CliError("Administrator device authorization expired; run `corvio-admin auth login` again.", {
|
|
205
|
+
code: "expired_token",
|
|
206
|
+
exitCode: EXIT.AUTH,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
await storeAdminCredential({ ...token, api_base: baseUrl(options) });
|
|
210
|
+
const authenticatedApi = new ApiClient({ baseUrl: baseUrl(options), credential: token.secret, userAgent: USER_AGENT });
|
|
211
|
+
const identity = await authenticatedApi.request("/me");
|
|
212
|
+
writeResult({
|
|
213
|
+
authenticated: true,
|
|
214
|
+
source: "device_link",
|
|
215
|
+
audience: identity.api_key?.audience,
|
|
216
|
+
scopes: identity.api_key?.scopes || [],
|
|
217
|
+
expires_at: token.expires_at,
|
|
218
|
+
}, { json: options.json });
|
|
219
|
+
}
|
|
220
|
+
|
|
66
221
|
async function authStatus(options) {
|
|
67
|
-
const
|
|
222
|
+
const credential = await readAdminCredential();
|
|
223
|
+
if (!credential) {
|
|
224
|
+
writeResult({ authenticated: false, sources: ["CORVIO_ADMIN_API_KEY", "device_link"] }, { json: options.json });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const api = new ApiClient({ baseUrl: baseUrl(options, credential), credential: credential.key, userAgent: USER_AGENT });
|
|
68
228
|
const identity = await api.request("/me");
|
|
69
229
|
if (identity.api_key?.audience !== "platform_admin") {
|
|
70
230
|
throw new CliError("The configured key is not a platform administrator credential.", {
|
|
@@ -72,23 +232,64 @@ async function authStatus(options) {
|
|
|
72
232
|
exitCode: EXIT.AUTH,
|
|
73
233
|
});
|
|
74
234
|
}
|
|
75
|
-
writeResult({ authenticated: true, source:
|
|
235
|
+
writeResult({ authenticated: true, source: credential.source, identity }, { json: options.json });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function authLogout(options) {
|
|
239
|
+
const credential = await readAdminCredential();
|
|
240
|
+
if (!credential) {
|
|
241
|
+
writeResult({ authenticated: false, changed: false }, { json: options.json });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (credential.source === "environment") {
|
|
245
|
+
throw new CliError("CORVIO_ADMIN_API_KEY is managed by the parent environment and cannot be removed by the CLI.", {
|
|
246
|
+
code: "environment_credential_managed_externally",
|
|
247
|
+
exitCode: EXIT.AUTH,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const api = new ApiClient({ baseUrl: baseUrl(options, credential), credential: credential.key, userAgent: USER_AGENT });
|
|
251
|
+
await api.request("/admin/auth/credentials/current:revoke", { method: "POST" });
|
|
252
|
+
await clearAdminCredential();
|
|
253
|
+
writeResult({ authenticated: false, revoked: true }, { json: options.json });
|
|
76
254
|
}
|
|
77
255
|
|
|
78
256
|
async function capabilities(options) {
|
|
79
|
-
const api = client(options);
|
|
257
|
+
const { api, credential } = await client(options);
|
|
80
258
|
const [index, identity] = await Promise.all([api.request("/admin"), api.request("/me")]);
|
|
81
|
-
writeResult({ cli_version: VERSION, api: index, principal: identity }, { json: options.json });
|
|
259
|
+
writeResult({ cli_version: VERSION, auth_source: credential.source, api: index, principal: identity }, { json: options.json });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function updateCheck(options) {
|
|
263
|
+
const { api } = await client(options);
|
|
264
|
+
const index = await api.request("/admin");
|
|
265
|
+
const result = await resolveNpmUpdateStatus({
|
|
266
|
+
currentVersion: VERSION,
|
|
267
|
+
policy: index?.distribution?.admin_cli || {
|
|
268
|
+
package: "@corvio/admin-cli",
|
|
269
|
+
dist_tag: "beta",
|
|
270
|
+
},
|
|
271
|
+
userAgent: USER_AGENT,
|
|
272
|
+
});
|
|
273
|
+
writeResult(result, { json: options.json });
|
|
82
274
|
}
|
|
83
275
|
|
|
84
276
|
async function catalog(options) {
|
|
85
|
-
|
|
277
|
+
const { api } = await client(options);
|
|
278
|
+
writeResult(await api.request("/admin/data/catalog"), { json: options.json });
|
|
86
279
|
}
|
|
87
280
|
|
|
88
281
|
async function query(options) {
|
|
89
282
|
let body;
|
|
90
283
|
if (options.file) {
|
|
91
|
-
|
|
284
|
+
try {
|
|
285
|
+
body = JSON.parse(await readTextFile(options.file));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error?.code === "ENOENT") throw error;
|
|
288
|
+
throw new CliError("--file must contain a valid JSON request object.", { code: "invalid_json_file" });
|
|
289
|
+
}
|
|
290
|
+
if (!body || Array.isArray(body) || typeof body !== "object") {
|
|
291
|
+
throw new CliError("--file must contain a JSON object.", { code: "invalid_json_file" });
|
|
292
|
+
}
|
|
92
293
|
if (options.purpose) body.purpose = options.purpose;
|
|
93
294
|
} else {
|
|
94
295
|
body = {
|
|
@@ -96,26 +297,56 @@ async function query(options) {
|
|
|
96
297
|
purpose: option(options, "purpose", { required: true }),
|
|
97
298
|
fields: listOption(options, "fields"),
|
|
98
299
|
group_by: listOption(options, "group-by"),
|
|
99
|
-
metrics: options.metrics ?
|
|
100
|
-
filters: options.filters ?
|
|
101
|
-
order_by: options["order-by"] ?
|
|
102
|
-
limit: numberOption(options, "limit", 100),
|
|
300
|
+
metrics: options.metrics ? parseJsonValue(options.metrics, "metrics") : [],
|
|
301
|
+
filters: options.filters ? parseJsonValue(options.filters, "filters") : [],
|
|
302
|
+
order_by: options["order-by"] ? parseJsonValue(options["order-by"], "order-by") : [],
|
|
303
|
+
limit: numberOption(options, "limit", 100, { maximum: 200 }),
|
|
103
304
|
cursor: options.cursor || null,
|
|
104
305
|
};
|
|
105
306
|
}
|
|
106
|
-
|
|
307
|
+
const { api } = await client(options);
|
|
308
|
+
writeResult(await api.request("/admin/data/query", { method: "POST", body }), { json: options.json });
|
|
107
309
|
}
|
|
108
310
|
|
|
109
311
|
async function listResource(options, resource) {
|
|
110
|
-
|
|
312
|
+
const { api } = await client(options);
|
|
313
|
+
writeResult(await api.request(`/admin/${resource}`, { query: listQuery(options) }), { json: options.json });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function downloadSkill(options) {
|
|
317
|
+
const { api } = await client(options);
|
|
318
|
+
const output = String(options.output || "corvio-admin-data.zip");
|
|
319
|
+
const manifest = await api.request("/admin/skill/manifest.json");
|
|
320
|
+
const bytes = await api.requestBytes("/admin/skill/corvio-admin-data.zip");
|
|
321
|
+
const actualHash = createHash("sha256").update(bytes).digest("hex");
|
|
322
|
+
if (!manifest.archive_sha256 || actualHash !== manifest.archive_sha256) {
|
|
323
|
+
throw new CliError("Administrator Skill archive did not match its manifest SHA-256.", {
|
|
324
|
+
code: "admin_skill_integrity_mismatch",
|
|
325
|
+
exitCode: EXIT.REMOTE,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const path = await writeBinaryFile(output, bytes);
|
|
329
|
+
writeResult({
|
|
330
|
+
downloaded: true,
|
|
331
|
+
output: path,
|
|
332
|
+
install: `npx skills add ${path}`,
|
|
333
|
+
}, { json: options.json });
|
|
111
334
|
}
|
|
112
335
|
|
|
113
336
|
async function main({ options, positionals }) {
|
|
114
337
|
const [command, action, id] = positionals;
|
|
115
338
|
if (options.version || command === "version") return writeResult(VERSION, { json: options.json });
|
|
116
339
|
if (options.help || !command || command === "help") return writeResult(HELP.trimEnd(), { json: false });
|
|
117
|
-
|
|
340
|
+
validateInvocation(options, positionals);
|
|
341
|
+
if (command === "auth") {
|
|
342
|
+
if (action === "login") return authLogin(options);
|
|
343
|
+
if (action === "status") return authStatus(options);
|
|
344
|
+
if (action === "logout") return authLogout(options);
|
|
345
|
+
throw new CliError("auth action must be login, status, or logout.");
|
|
346
|
+
}
|
|
118
347
|
if (command === "capabilities") return capabilities(options);
|
|
348
|
+
if (command === "update" && (action || "check") === "check") return updateCheck(options);
|
|
349
|
+
if (command === "skill" && action === "download") return downloadSkill(options);
|
|
119
350
|
if (command === "catalog") return catalog(options);
|
|
120
351
|
if (command === "query") return query(options);
|
|
121
352
|
if (command === "data" && action === "query") return query(options);
|
|
@@ -126,15 +357,18 @@ async function main({ options, positionals }) {
|
|
|
126
357
|
if (command === "runs" && action === "content") {
|
|
127
358
|
const runId = id || option(options, "id", { required: true });
|
|
128
359
|
const purpose = option(options, "purpose", { required: true });
|
|
129
|
-
|
|
360
|
+
const { api } = await client(options);
|
|
361
|
+
return writeResult(await api.request(`/admin/runs/${resourcePath(runId)}/content`, { query: { purpose } }), { json: options.json });
|
|
130
362
|
}
|
|
131
363
|
if (command === "usage" && action === "events") return listResource(options, "usage/events");
|
|
132
364
|
if (command === "usage" && action === "summary") {
|
|
133
|
-
|
|
365
|
+
const { api } = await client(options);
|
|
366
|
+
return writeResult(await api.request("/admin/usage/summary", { query: listQuery(options) }), { json: options.json });
|
|
134
367
|
}
|
|
135
368
|
if (command === "receipts" && action === "get") {
|
|
136
369
|
const receiptId = id || option(options, "id", { required: true });
|
|
137
|
-
|
|
370
|
+
const { api } = await client(options);
|
|
371
|
+
return writeResult(await api.request(`/admin/data/query-receipts/${resourcePath(receiptId)}`), { json: options.json });
|
|
138
372
|
}
|
|
139
373
|
throw new CliError(`Unknown administrator command: ${positionals.join(" ")}`);
|
|
140
374
|
}
|